1

「C++には何もしないので、配置削除は必要ありません」と聞いたことがあります。削除式に関しては、C++の「配置削除」の欠如

次のコードを考えてみましょう:あなたがここにデストラクタを見ることができるように

#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点のために失敗します。

1は、ここで説明するように、オブジェクトの配列を構築するために[]新しい配置を使用する場合にも同様の問題が発生することを注意:ただし、これは単純に配列のサイズを保存することで、多くの問題なしに回避することができ Global "placement" delete[]

あなたのメモリブロックの先頭で、配列のコンストラクタ/デストラクタの呼び出しを、単一のオブジェクト配置の新しい/明示的なデストラクタ呼び出しを使用してループ内で手動で処理します。

一方、私は多重継承の問題を解決するための優雅な方法を考えることはできません。削除式内の元のポインタから元のポインタを取得する「マジック」コードは実装固有のものであり、配列のように「手動で行う」という単純な方法はありません。ここで

は、これが問題となる別の状況は、それを回避するために醜いハックして、次のとおりです。

#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

関連する問題