调用析构函数然后构造函数(重置对象)

时间:2009-07-14 10:46:14

标签: c++ memory-management new-operator

我想重置一个对象。我可以通过以下方式完成吗?

anObject->~AnObject();
anObject = new(anObject) AnObject();
// edit: this is not allowed: anObject->AnObject();

此代码显然是在placement new中分配的对象的典型生命周期的子集:

AnObject* anObject = malloc(sizeof(AnObject));
anObject = new (anObject) AnObject(); // My step 2.
// ...
anObject->~AnObject(); // My step 1.
free(anObject)
// EDIT: The fact I used malloc instead of new doesn't carry any meaning

唯一改变的是构造函数和析构函数调用的顺序。

那么,为什么在 following FAQ 所有的威胁出现了吗?

  

[11.9]但我可以明确地打电话给   析构函数,如果我已经分配了我的对象   新的?

     

常见问题解答:你不能,除非对象是   分配了新的位置。对象   必须删除由new创建的   做两件事(记住他们):电话   析构函数,然后释放内存。

     

FQA:翻译:删除是一种方式   明确地称为析构函数,但它   也解除了记忆。您可以   也没有调用析构函数   释放内存。这很丑陋   在大多数情况下没用,但你可以做到   这一点。

析构函数/构造函数调用显然是普通的C ++代码。代码中使用的保证直接来自放置新保证。它是标准的核心,它是坚如磐石的东西。它怎么能被称为“脏”并被表现为不可靠的东西?

您认为新的插入式和非插入式实施是否可能有所不同?我正在考虑一些病态的可能性,例如,常规的新的可以放置在块之前分配的内存块的大小,其中放置新的显然不会这样做(因为它不分配任何内存)。这可能会导致某些问题出现差距...... 这样的新()实现是否可行?

10 个答案:

答案 0 :(得分:25)

不要被FQA巨魔所吸引。像往常一样,他弄错了事实。

你当然可以直接为所有对象调用析构函数,无论它们是否使用placement new创建。丑陋是在旁观者的眼中,它确实很少需要,但唯一的事实是内存分配和对象创建必须平衡。

“Regular”new / delete通过将内存分配和对象创建结合在一起简化了这一点,并且堆栈分配通过为您完成这两项操作进一步简化了它。

但是,以下内容完全合法:

int foo() {
    CBar bar;
    (&bar)->~CBar();
    new (&bar) CBar(42);
 }

两个对象都被破坏,堆栈内存也会自动回收。但与FQA声明不同的是,析构函数的第一次调用之前没有放置新的。

答案 1 :(得分:19)

为什么不实现一个Clear()方法,它可以解析析构函数体内的代码呢?析构函数然后只调用Clear()并直接在对象上调用Clear()来“重置”。

另一个选项,假设您的类正确支持赋值:

MyClass a;
...
a = MyClass();

我使用此模式重置std :: stack实例,就像堆栈适配器一样 没有提供明确的功能。

答案 2 :(得分:14)

从技术上讲,明确调用构造函数或析构函数是不好的做法。

delete关键字在您使用时最终会调用它们。对于构造函数的新功能也是如此。

我很抱歉,但是这段代码让我想把头发撕掉。你应该这样做:

分配对象的新实例

AnObject* anObject = new AnObject();

删除对象的实例

delete anObject;

永远不要这样做:

anObject->~AnObject(); // My step 1.
free(anObject)

如果你必须“重置”一个对象,要么创建一个方法来清除里面的所有实例变量,要么我建议你做的是删除对象并为自己分配一个新对象。

  

“这是该语言的核心?”

这意味着什么。 Perl有大约六种编写for循环的方法。仅仅因为你可以用语言做事,因为它们是受支持的,这意味着你应该使用它们。哎呀我可以使用 for switch 语句编写我的所有代码,因为语言的“核心”支持它们。这不是一个好主意。

为什么在明显不必使用 malloc 时使用 malloc 。 Malloc是一种C方法。

New和Delete是您在C ++中的朋友

“重置”对象

myObject.Reset();

你去吧。这样可以避免以危险的方式不必要地分配和释放内存。编写Reset()方法以清除类中所有对象的值。

答案 3 :(得分:9)

您无法以您指定的方式调用构造函数。相反,您可以使用placement-new(例如您的代码也表示):

new (anObject) AnObject();

如果内存位置仍然可用,则保证此代码定义良好 - 就像在您的情况下一样。

(我已经删除了关于这是否是有争议的代码的部分 - 它是明确定义的。完全停止。)

顺便说一下,Brock是对的:如何解释delete的实现 - 它不是而不是与调用析构函数相同,后跟free。始终将newdelete的来电配对,永远不要将其中一个调用: 未定义。

答案 4 :(得分:7)

请注意,它们不是mallocfree,而是operator newoperator delete。此外,与您的代码不同,通过使用新的,您可以保证异常安全。几乎相同的代码如下。

AnObject* anObject = ::operator new(sizeof(AnObject));
try
{
    anObject = new (anObject) AnObject();
}
catch (...)
{
    ::operator delete(anObject);
    throw;
}

anObject->~AnObject();
::operator delete(anObject)

您提出的重置是有效的,但不是惯用的。很难做到正确,因此通常不赞成并且气馁。

答案 5 :(得分:4)

你不能像这样调用构造函数,但重新使用内存并调用placement new是没有错的,只要你删除或释放(释放)内存。我必须说重新设置像这样的物体有点粗略。我会编写一个可明确重置的对象,或编写一个交换方法并使用它来重置它。

E.g。

anObject.swap( AnObject() ); // swap with "clean" object

答案 6 :(得分:3)

如果你的对象具有合理的赋值语义(并且正确的operator =),则* anObject = AnObject()更有意义,并且更容易理解。

答案 7 :(得分:3)

是的,你在做什么在大多数情况下是有效的。 [basic.life]p8说:

  

如果在对象的生命周期结束之后并且在重用或释放对象占用的存储之前,则在原始对象占用的存储位置创建新对象,指向原始对象的指针,引用原始对象的引用,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用于操作新对象,如果:

     
      
  • 新对象的存储空间恰好覆盖原始对象占用的存储位置,

  •   
  • 新对象与原始对象的类型相同(忽略顶级cv限定符),

  •   
  • 原始对象的类型不是const限定的,如果是类类型,则不包含任何类型为const限定或引用类型的非静态数据成员,并且

  •   
  • 原始对象和新对象都不是潜在重叠的子对象([intro.object])。

  •   

如果您没有const或参考成员,那么这是合法的。

如果您不具备此保证,则需要使用std::launder或使用新位置返回的指针(如您还要做的话),如果您想使用新对象:

// no const/ref members
anObject->~AnObject(); // destroy object
new (anObject) AnObject(); // create new object in same storage, ok

anObject->f(); // ok

// const/ref members
anObject->~AnObject();
auto newObject = new (anObject) AnObject();

anObject->f(); // UB
newObject->f(); // ok
std::launder(anObject)->f(); // ok

答案 8 :(得分:1)

向对象添加类似Reset()方法的东西要好得多,而不是使用贴图新方法。

您正在利用展示位置新功能,该功能旨在让您控制对象的分配位置。如果您的硬件具有像闪存芯片一样的“特殊”内存,这通常只是一个问题。如果你想在闪存芯片中放置一些物体,你可以使用这种技术。它允许您显式调用析构函数的原因是您现在控制了内存,因此C ++编译器不知道如何执行删除的释放部分。

使用重置方法也不会为您节省太多代码,您必须将成员设置为其起始值。 malloc()没有这样做,所以你不得不在构造函数中编写代码。只需创建一个将成员设置为起始值的函数,调用它Reset()从构造函数以及您需要的任何其他位置调用它。

答案 9 :(得分:1)

为什么不使用operator =()重置?这不是有争议的,而且更具可读性。

A a;
//do something that changes the state of a
a = A(); // reset the thing