我在想:他们说如果您手动调用析构函数-您做错了什么。但是,总是这样吗?有没有反例?需要手动调用它或很难/不可能/不切实际地避免它的情况?
new
来初始化一个新对象来代替旧对象。通常这不是一个好主意,但这并不是闻所未闻的。
我在想:他们说如果您手动调用析构函数-您做错了什么。但是,总是这样吗?有没有反例?需要手动调用它或很难/不可能/不切实际地避免它的情况?
new
来初始化一个新对象来代替旧对象。通常这不是一个好主意,但这并不是闻所未闻的。
Answers:
如果对象是使用的重载形式构造的,则需要手动调用析构函数operator new()
,除非使用“ std::nothrow
”重载:
T* t0 = new(std::nothrow) T();
delete t0; // OK: std::nothrow overload
void* buffer = malloc(sizeof(T));
T* t1 = new(buffer) T();
t1->~T(); // required: delete t1 would be wrong
free(buffer);
但是,像上面显式调用析构函数那样,在较低级别上进行外部内存管理是设计不良的标志。可能实际上,这不仅是错误的设计,而且是完全错误的(是的,在赋值运算符中使用显式析构函数后再进行复制构造函数调用是错误的设计,并且很可能是错误的)。
在C ++ 2011中,还有一个使用显式析构函数调用的原因:使用广义联合时,有必要在更改表示对象的类型时显式销毁当前对象并使用placement new创建一个新对象。另外,在销毁并集时,如果需要销毁,则必须显式调用当前对象的析构函数。
operator new
”,不如说是“使用placement new
”。
operator new(std::size_t, void*)
(以及数组的变体),而且还谈论了所有的重载版本operator new()
。
temp = Class(object); temp.operation(); object.~Class(); object = Class(temp); temp.~Class();
yes, using an explicit destructor followed by a copy constructor call in the assignment operator is a bad design and likely to be wrong
。为什么这样说 我认为,如果析构函数是微不足道的或接近微不足道的,则其开销最小,并且会增加DRY原理的使用。如果在这种情况下使用move operator=()
,它甚至可能比使用swap更好。YMMV。
virtual
函数时(这virtual
将不会重新创建函数),否则实际上就是一个问题,否则该对象仅是部分[重新]构造的。
所有答案都描述了具体情况,但是有一个通用答案:
每当您只需要销毁对象(从C ++角度而言)而不释放对象所驻留的内存时,都可以显式调用dtor 。
这通常发生在所有与对象构造/销毁无关地管理内存分配/释放的情况下。在那些情况下,构建是通过在现有内存块上放置new来进行的,而销毁是通过显式dtor调用发生的。
这是原始示例:
{
char buffer[sizeof(MyClass)];
{
MyClass* p = new(buffer)MyClass;
p->dosomething();
p->~MyClass();
}
{
MyClass* p = new(buffer)MyClass;
p->dosomething();
p->~MyClass();
}
}
另一个值得注意的示例是std::allocator
当由时使用的默认值std::vector
:元素是在vector
期间构造的push_back
,但是内存是按块分配的,因此它预先存在元素构造。因此,vector::erase
必须销毁元素,但不一定要取消分配内存(特别是如果必须很快发生新的push_back ...)。
从严格的OOP角度来看,这是“错误的设计”(您应该管理对象,而不是内存:事实上,需要内存的对象是一个“事件”),在“低级编程”中或在内存为零的情况下,它是“良好的设计”。不从默认operator new
购买的“免费商店”中提取。
如果它随机出现在代码周围是不好的设计,如果它是专门针对该目的设计的类在本地发生则是好的设计。
不,要视情况而定,有时这是合法且良好的设计。
为了理解为什么以及何时需要显式调用析构函数,让我们看看“ new”和“ delete”发生了什么。
要动态创建对象,请执行 T* t = new T;
以下操作:1.分配了sizeof(T)内存。2.调用T的构造函数以初始化分配的内存。new运算符执行两件事:分配和初始化。
破坏delete t;
引擎盖下的对象:1.调用T的析构函数。2.释放为该对象分配的内存。操作员删除还做两件事:销毁和释放。
编写构造函数进行初始化,编写析构函数进行销毁。当您显式调用析构函数时,仅会完成销毁,而不会进行释放。
因此,显式调用析构函数的合法使用可能是:“我只想破坏对象,但是我(尚未)不能(或者不能)释放内存分配。”
一个常见的例子是为某些对象池预分配内存,否则必须动态分配这些对象。
创建新对象时,您将从预分配的池中获取内存块并进行“新放置”。处理完对象后,您可能希望显式调用析构函数以完成清理工作(如果有)。但是您实际上不会像操作员删除操作那样实际释放内存。取而代之的是,您将块返回池中以进行重用。
在某些情况下有必要时:
在我工作的代码中,我在分配器中使用显式析构函数调用,我实现了简单的分配器的实现,该分配器使用new放置将内存块返回到stl容器。在销毁中,我有:
void destroy (pointer p) {
// destroy objects by calling their destructor
p->~T();
}
在构造时:
void construct (pointer p, const T& value) {
// initialize memory with placement new
#undef new
::new((PVOID)p) T(value);
}
使用平台特定的alloc和dealloc机制,在allocate()中完成分配,并在deallocate()中完成内存释放。该分配器用于绕过doug lea malloc,并直接在Windows上使用例如LocalAlloc。
我发现3次需要这样做的场合:
我从未遇到过需要手动调用析构函数的情况。我似乎还记得,甚至Stroustrup也声称这是不好的做法。
C+
☺的创作者
那这个呢?
如果从构造函数中抛出异常,则不会调用Destructor,因此我必须手动调用它以销毁在异常之前在构造函数中创建的句柄。
class MyClass {
HANDLE h1,h2;
public:
MyClass() {
// handles have to be created first
h1=SomeAPIToCreateA();
h2=SomeAPIToCreateB();
try {
...
if(error) {
throw MyException();
}
}
catch(...) {
this->~MyClass();
throw;
}
}
~MyClass() {
SomeAPIToDestroyA(h1);
SomeAPIToDestroyB(h2);
}
};
ctor
正是由于您自己提供的原因,您在此处编写方法是错误的:如果资源分配失败,则清理存在问题。“ ctor”不应致电this->~dtor()
。dtor
应该在构造对象上调用,在这种情况下,该对象尚未构造。无论发生什么情况,ctor
都应处理清除。在ctor
代码内部,您应该使用utils之类的工具std::unique_ptr
来在发生异常时为您自动清理。更改HANDLE h1, h2
类中的字段以支持自动清除也是一个不错的主意。
MyClass(){ cleanupGuard1<HANDLE> tmp_h1(&SomeAPIToDestroyA) = SomeAPIToCreateA(); cleanupGuard2<HANDLE> tmp_h2(&SomeAPIToDestroyB) = SomeAPIToCreateB(); if(error) { throw MyException(); } this->h1 = tmp_h1.release(); this->h2 = tmp_h2.release(); }
和就是这样。无需冒险进行手动清理,也无需将句柄存储在部分构造的对象中,直到一切安全为止。如果您HANDLE h1,h2
将类更改为cleanupGuard<HANDLE> h1;
etc,那么您甚至根本不需要dtor
。
cleanupGuard1
并cleanupGuard2
取决于什么做了相关的xxxToCreate
回报,什么参数做了相关xxxxToDestroy
服食。如果它们很简单,您甚至可能不需要编写任何东西,因为经常会发现std::unique_ptr<x,deleter()>
(或类似的东西)可以在两种情况下为您解决问题。
找到了另一个示例,在该示例中,您将必须手动调用析构函数。假设您实现了一个类似变体的类,其中包含几种数据类型之一:
struct Variant {
union {
std::string str;
int num;
bool b;
};
enum Type { Str, Int, Bool } type;
};
如果Variant
实例持有一个std::string
,现在您要为该联合分配其他类型,则必须销毁第std::string
一个。编译器不会自动执行此操作。
在另一种情况下,我认为调用析构函数是完全合理的。
在编写“ Reset”类型的方法以将对象恢复到其初始状态时,调用Destructor删除要重置的旧数据是完全合理的。
class Widget
{
private:
char* pDataText { NULL };
int idNumber { 0 };
public:
void Setup() { pDataText = new char[100]; }
~Widget() { delete pDataText; }
void Reset()
{
Widget blankWidget;
this->~Widget(); // Manually delete the current object using the dtor
*this = blankObject; // Copy a blank object to the this-object.
}
};
cleanup()
在这种情况下以及在析构函数中声明了要调用的特殊方法,它看起来是否更干净?