关于删除表达式,C++缺乏"placement delete"



我听人说"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("OKnn");
return ret;
}
template<typename T>
void customDelete1(T *ptr) {
printf("customDelete1...n");
delete ptr;
printf("OKnn");
}
////////////////////////////////
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("OKnn");
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("OKnn");
}
////////////////////////////////////////////////////////////////
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: %xn", c1);
printf("a: %xn", a1);
printf("b: %xn", b1);
printf("n");
customDelete1(b1); // <- this will work, the delete expression offsets b1 before deallocing
printf("--------------nn");
C *c2 = customNew2<C>();
A *a2 = c2;
B *b2 = c2;
printf("c: %xn", c2);
printf("a: %xn", a2);
printf("b: %xn", 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("--------------nn");
return 0;
}

正如你在这里看到的,析构函数,作为虚拟的,都被正确调用,但 b2 的释放仍然会失败,因为 b2 指向与 c2 不同的地址。

请注意,当使用放置 new[] 构造对象数组时,会出现类似的问题,如下所述: 全局"放置"删除[]

但是,只需将数组大小保存在内存块的头部,并使用单个对象放置新的/显式析构函数调用在循环中手动处理数组构造函数/析构函数调用,就可以毫不费力地解决此问题。

另一方面,我想不出任何优雅的方法来解决多重继承的问题。从删除表达式中的基指针检索原始指针的"魔术"代码是特定于实现的,并且没有像数组那样"手动执行"的简单方法。

这是另一种情况,这成为一个问题,有一个丑陋的黑客来解决它:

#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("OKnn");
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("OKnn");
}
////////////////////////////////////////////////////////////////
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: %xn", c);
printf("a: %xn", a);
printf("b: %xn", b);
printf("n");
// now it works
customDelete(alloc, b);
printf("--------------nn");
return 0;
}

这不是一个真正的问题,而只是一个咆哮,因为我相当确定不存在获取地址的魔术运算符或独立于平台的方法。在我工作的公司,我们有一个库,它使用自定义分配器与上面的hack,工作正常,直到我们不得不将其与另一个需要替换全局new/delete的程序静态链接。我们当前的解决方案只是通过指向基的指针来禁止删除对象,该指针无法显示始终与派生最多的对象具有相同的地址,但这似乎有点不幸。"ptr->~T();free(ptr);"似乎是一种足够常见的模式,许多人似乎认为它等同于删除表达式,但事实并非如此。我很好奇是否有其他人遇到过这个问题以及他们是如何设法解决这个问题的。

如果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));
}

最新更新