我听到有人说“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的释放仍然会失败,因为B2点,比C2不同的地址。
注意,当一个使用放置新的[]构造对象的数组,如这里所描述的类似的问题出现了:通过简单地保存数组的大小 Global "placement" delete[]
然而,这可周围没有太多的麻烦工作内存块的头部和处理数组构造函数/析构函数的方法在使用单个对象放置新/显式析构函数调用的循环中手动调用。
另一方面,我想不出任何优雅的方式来解决多重继承的问题。从“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;
}
这是不是一个问题实际上更多的只是一个夸夸其谈的为我相当肯定,没有魔术师或平台独立的方法来获取地址存在。在我工作的公司里,我们有一个库,它使用自定义分配器,其上面的黑客工作正常,直到我们必须静态链接到另一个需要替换全局新/删除的程序。我们当前的解决方案是简单地禁止通过指向基础的指针来删除对象,该指针不能显示为始终与最派生的对象具有相同的地址,但这似乎有点不幸。 “ptr->〜T(); free(ptr);”似乎是一种常见的模式,许多人似乎认为它相当于删除表达式,但事实并非如此。我很好奇,如果有其他人遇到了这个问题,他们是如何设法解决这个问题的。
'dynamic_cast'就是这样神奇的操作符。 –
只是一个简短的提示,如果我需要这样的东西,我会使用像scoped_ptr或shared_ptr这样的对象与自定义删除器。这可以用来将对象放回队列或自定义堆,但也可以用于删除/销毁具体类型。 – Sven
在这种情况下,dynamic_cast几乎就是我正在寻找的。感觉有点不知道这种用法。我意识到智能指针和自定义删除器,不幸的是,所讨论的库的设计方式并不便于使用。 –
Anon49343283