1

사람들은 "C++은 배치를 삭제할 필요가 없기 때문에 아무 것도하지 않을 것"이라고 말한 것으로 들었습니다.삭제 표현에 관해서는 C++에서 "placement delete"가 없다는 것

는 다음과 같은 코드를 생각해 보면 소멸자가 가상되고, 모두 제대로이라고 여기에서 볼 수 있듯이

#include <cstdlib> 
#include <cstdio> 
#include <new> 

//////////////////////////////////////////////////////////////// 

template<typename T, typename... ARGS> 
T* customNew1(ARGS&&... args) { 
    printf("customNew1...\n"); 
    auto ret = new T { std::forward<ARGS>(args)... }; 
    printf("OK\n\n"); 
    return ret; 
} 

template<typename T> 
void customDelete1(T *ptr) { 
    printf("customDelete1...\n"); 
    delete ptr; 
    printf("OK\n\n"); 
} 

//////////////////////////////// 

template<typename T, typename... ARGS> 
T* customNew2(ARGS&&... args) { 
    printf("customNew2 alloc...\n"); 
    void *buf = std::malloc(sizeof(T)); 
    printf("customNew2 construct...\n"); 
    auto ret = ::new(buf) T { std::forward<ARGS>(args)... }; 
    printf("OK\n\n"); 
    return ret; 
} 

template<typename T> 
void customDelete2(T *ptr) { 
    printf("customDelete2 destruct...\n"); 

    // what I want: a "placement delete" which calls the destructor and returns the address that should be passed to the deallocation function 
    // e.g. 
    // 
    // void* ptrToFree = ::delete(ptr); 
    // std::free(ptrToFree); 
    // 
    // equally fine would be a "magic" operator that allows one to obtain said address without actually calling the destructor: 
    // 
    // void* ptrToFree = get_deallocation_address_of(ptr); 
    // ptr->~T(); 
    // std::free(ptrToFree); 

    ptr->~T(); 
    printf("customDelete2 free...\n"); 
    std::free(ptr); 
    printf("OK\n\n"); 
} 

//////////////////////////////////////////////////////////////// 

struct A { 
    int a; 
    A() : a(0) { 
     printf("A()\n"); 
    } 
    virtual ~A() { 
     printf("~A()\n"); 
    } 
}; 

struct B { 
    int b; 
    B() : b(0) { 
     printf("B()\n"); 
    } 
    virtual ~B() { 
     printf("~B()\n"); 
    } 
}; 

struct C : A, B { 
    int c; 
    C() : c(0) { 
     printf("C()\n"); 
    } 
    ~C() { 
     printf("~C()\n"); 
    } 
}; 

//////////////////////////////////////////////////////////////// 

int main() { 

    C *c1 = customNew1<C>(); 
    A *a1 = c1; 
    B *b1 = c1; 

    // Assume c and a will be the same but b is offset 
    printf("c: %x\n", c1); 
    printf("a: %x\n", a1); 
    printf("b: %x\n", b1); 
    printf("\n"); 

    customDelete1(b1); // <- this will work, the delete expression offsets b1 before deallocing 

    printf("--------------\n\n"); 

    C *c2 = customNew2<C>(); 
    A *a2 = c2; 
    B *b2 = c2; 

    printf("c: %x\n", c2); 
    printf("a: %x\n", a2); 
    printf("b: %x\n", b2); 
    printf("\n"); 

    // customDelete2(b2); // <- this will break 
    customDelete2(a2); // <- this will work because a2 happens to point at the same address as c2 

    printf("--------------\n\n"); 

    return 0; 
} 

을하지만, B2의 해제는 여전히 C2가 아닌 다른 주소에서 B2 점 때문에 실패합니다. Global "placement" delete[]

그러나이 큰 문제없이 해결할 수있다 단순히에 배열 크기를 저장하여 여기 설명 된 바와 같이 하나의 새로운 [] 오브젝트의 배열을 구성하는 배치를 사용하는 경우와 유사한 문제가 발생

참고 메모리 블럭의 헤드이며 단일 객체 배치 새로운/명시 적 소멸자 호출을 사용하여 배열 생성자/소멸자 호출을 루프에서 수동으로 처리합니다.

한편, 다중 상속 문제를 해결할 수있는 유익한 방법은 생각할 수 없습니다. delete 표현식 내의 기본 포인터에서 원래 포인터를 가져 오는 "magic"코드는 구현에 따라 다르며 배열과 마찬가지로 "수동으로 수행"하는 간단한 방법은 없습니다.

#include <cstdlib> 
#include <cstdio> 
#include <new> 

//////////////////////////////////////////////////////////////// 

// imagine this is a library in which all allocations/deallocations must be handled by this base interface 
class Alloc { 
public: 
    virtual void* alloc(std::size_t sz) =0; 
    virtual void free(void *ptr) =0; 
}; 

// here is version which uses the normal allocation functions 
class NormalAlloc : public Alloc { 
public: 
    void* alloc(std::size_t sz) override final { 
     return std::malloc(sz); 
    } 
    void free(void *ptr) override final { 
     std::free(ptr); 
    } 
}; 

// imagine we have a bunch of other versions like this that use different allocation schemes/memory heaps/etc. 
class SuperEfficientAlloc : public Alloc { 
    void* alloc(std::size_t sz) override final { 
     // some routine for allocating super efficient memory... 
     (void)sz; 
     return nullptr; 
    } 
    void free(void *ptr) override final { 
     // some routine for freeing super efficient memory... 
     (void)ptr; 
    } 
}; 

// etc... 

//////////////////////////////// 

// in this library we will never call new or delete, instead we will always use the below functions 

// this is used instead of new... 
template<typename T, typename... ARGS> 
T* customNew(Alloc &alloc, ARGS&&... args) { 
    printf("customNew alloc...\n"); 
    void *buf = alloc.alloc(sizeof(T)); 
    printf("customNew construct...\n"); 
    auto ret = ::new(buf) T { std::forward<ARGS>(args)... }; 
    printf("OK\n\n"); 
    return ret; 
} 

// um... 
thread_local Alloc *stupidHack = nullptr; 

// unfortunately we also have to replace the global delete in order for this hack to work 
void operator delete(void *ptr) { 
    if (stupidHack) { 
     // the ptr that gets passed here is pointing at the right spot thanks to the delete expression below 
     // alloc has been stored in "stupidHack" since it can't be passed as an argument... 
     printf("customDelete free @ %x...\n", ptr); 
     stupidHack->free(ptr); 
     stupidHack = nullptr; 
    } else { 
     // well fug :-D 
    } 
} 

// ...and this is used instead of delete 
template<typename T> 
void customDelete(Alloc &alloc, T *ptr) { 
    printf("customDelete destruct @ %x...\n", ptr); 
    // set this here so we can use it in operator delete above 
    stupidHack = &alloc; 
    // this calls the destructor and offsets the pointer to the right spot to be dealloc'd 
    delete ptr; 
    printf("OK\n\n"); 
} 

//////////////////////////////////////////////////////////////// 

struct A { 
    int a; 
    A() : a(0) { 
     printf("A()\n"); 
    } 
    virtual ~A() { 
     printf("~A()\n"); 
    } 
}; 

struct B { 
    int b; 
    B() : b(0) { 
     printf("B()\n"); 
    } 
    virtual ~B() { 
     printf("~B()\n"); 
    } 
}; 

struct C : A, B { 
    int c; 
    C() : c(0) { 
     printf("C()\n"); 
    } 
    ~C() { 
     printf("~C()\n"); 
    } 
}; 

//////////////////////////////////////////////////////////////// 

int main() { 

    NormalAlloc alloc; 

    C *c = customNew<C>(alloc); 
    A *a = c; 
    B *b = c; 

    printf("c: %x\n", c); 
    printf("a: %x\n", a); 
    printf("b: %x\n", b); 
    printf("\n"); 

    // now it works 
    customDelete(alloc, b); 

    printf("--------------\n\n"); 

    return 0; 
} 

이 질문 내가 어떤 것을 매우 확신하지 않기 때문에 단지 호언 장담 정말 더되지 않은 : 여기

이 그것을 해결하기 위해 추한 해킹, 문제가되고 또 다른 상황은 마법 연산자 또는 플랫폼 독립적 인 방법으로 주소를 얻습니다. 내가 일하는 회사에서 글로벌 new/delete를 대체 할 필요가있는 다른 프로그램과 정적으로 링크해야만 할 때까지 위의 해킹으로 사용자 정의 할당자를 사용하는 라이브러리를 만들었습니다. 우리의 현재 솔루션은 단순히 가장 파생 된 객체와 같은 주소를 가질 수없는 기본에 대한 포인터를 통해 객체를 삭제하는 것을 금지하지만 이는 약간 불행한 것으로 보입니다. "ptr-> ~ T(); 자유 (ptr);" 일반적으로 충분한 패턴 인 것처럼 보이고 많은 사람들이 삭제 표현과 같다고 생각하는 것 같지만 그렇지 않습니다. 다른 사람들이이 문제를 겪고 어떻게 해결했는지 궁금합니다.

+1

'dynamic_cast '은 마술 같은 연산자입니다. –

+0

간단한 힌트, 그런 것들이 필요하다면 나는 scoped_ptr이나 shared_ptr 같은 객체를 커스텀 Deleter와 함께 사용한다. 이 객체를 사용하여 객체를 대기열 또는 사용자 정의 힙에 다시 배치 할 수 있지만 삭제/제거시 구체 유형을 사용하는 데 사용할 수도 있습니다. – Sven

+0

이 경우 dynamic_cast 은 제가 찾고있는 것입니다. 이것을 사용하는 것에 대해 모르는 것에 대해 멍청한 느낌. 불행히도 문제의 라이브러리는 사용하기 쉽도록 스마트 포인터와 사용자 정의 삭제자를 인식합니다. – Anon49343283

답변

2

p이 다형 클래스 유형의 객체를 가리키는 경우 dynamic_cast<void*>(p)을 사용하여 가장 많이 파생 된 객체의 주소를 가져올 수 있습니다. 다음과 같이 따라서 당신의 customDelete2이 구현 될 수

template <class T> 
void customDelete2(const T *ptr) { 
    const void* ptr_to_free = dynamic_cast<const void*>(ptr); 
    ptr->~T(); 
    std::free(const_cast<void*>(ptr_to_free)); 
} 

(예, 동적 const 객체를 할당 할 수 있습니다.)

이는 다형성 클래스 유형에 대해 컴파일 때문에, 당신은 dynamic_cast을 제거 할 수 있습니다 도우미 함수 :

template <class T> 
const void* get_complete_object_address(const T* p, std::true_type) { 
    return dynamic_cast<const void*>(p); 
} 

template <class T> 
const void* get_complete_object_address(const T* p, std::false_type) { 
    return p; 
} 

template <class T> 
void customDelete2(const T *ptr) { 
    const void* ptr_to_free = get_complete_object_address(
     ptr, 
     std::integral_constant<bool, std::is_polymorphic<T>::value>{} 
    ); 
    ptr->~T(); 
    free(const_cast<void*>(ptr_to_free)); 
} 
+0

와우, 아시다시피 ... 고마워. – Anon49343283