26

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

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

这段代码显然是由 inplace 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

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

那么,为什么在下面的常见问题解答 中出现了所有的威胁?

[11.9] 但是,如果我用 new 分配了我的对象,我可以显式调用析构函数吗?

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

FQA:翻译:delete 是一种显式调用析构函数的方法,但它也会释放内存。您也可以在不释放内存的情况下调用析构函数。在大多数情况下它是丑陋和无用的,但你可以这样做。

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

您认为 new 的就地实施和非就地实施有可能不同吗?我正在考虑一些病态的可能性,例如常规 new 可以将分配的内存块的大小放在块之前,而就地 new 显然不会这样做(因为它不分配任何内存)。这可能会导致一些问题的空白......这样的 new() 实现可能吗?

4

10 回答 10

31

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

您当然可以直接为所有对象调用析构函数,无论它们是否使用新位置创建。Ugly 是旁观者,确实很少需要,但唯一硬的事实是,内存分配和对象创建都必须平衡。

“常规” new/delete 通过将内存分配和对象创建联系在一起来简化这一点,而堆栈分配通过为您做这两者来进一步简化它。

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

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

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

于 2009-07-14T11:18:30.690 回答
21

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

另一种选择,假设您的班级正确支持分配:

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

我使用这种模式来重置 std::stack 实例,因为堆栈适配器不提供明确的功能。

于 2009-07-14T10:53:56.013 回答
13

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

当您使用 delete 关键字时,它最终会调用它们。构造函数的 new 也是如此。

我很抱歉,但那个代码让我想把头发扯下来。你应该这样做:

分配对象的新实例

AnObject* anObject = new AnObject();

删除对象的实例

delete anObject;

永远不要这样做:

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

如果您必须“重置”一个对象,请创建一个清除内部所有实例变量的方法,或者我建议您做的是删除该对象并为自己分配一个新对象。

“它是语言的核心?”

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

为什么你显然不需要使用malloc 。Malloc 是一种 C 方法。

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

“重置”对象

myObject.Reset();

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

于 2009-07-14T10:49:38.563 回答
9

您不能以您指定的方式调用构造函数。相反,您可以使用placement-new 来执行此操作(就像您的代码也表明的那样):

new (anObject) AnObject();

如果内存位置仍然可用,则可以保证此代码是明确定义的——就像您的情况一样。

(我已经删除了关于这是否是有争议的代码的部分——它定义明确。句号。)

顺便说一句,Brock 是对的: 的实现方式delete是不固定的——它调用析构函数不同,然后是free. new始终将and的调用配对delete,永远不要将一个与另一个混在一起:这是未定义的。

于 2009-07-14T10:49:14.290 回答
6

请注意,它们不是mallocand freethat 被使用,但是operator newand operator delete。此外,与您的代码不同,使用 new 可以保证异常安全。几乎等效的代码如下。

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

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

您提出的重置是有效的,但不是惯用的。很难做到正确,因此通常不受欢迎和气馁。

于 2009-07-14T10:50:49.247 回答
6

是的,你所做的大部分时间都是有效的。[basic.life]p8说:

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

  • 新对象的存储恰好覆盖了原始对象占用的存储位置,并且

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

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

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

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

如果你没有这个保证,std::launder如果你想使用新对象,你需要使用或使用placement new返回的指针(就像你正在做的那样):

// 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
于 2018-05-07T19:28:00.913 回答
4

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

A a;
//do something that changes the state of a
a = A(); // reset the thing
于 2018-05-08T08:04:35.583 回答
3

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

例如

anObject.swap( AnObject() ); // swap with "clean" object
于 2009-07-14T10:54:40.363 回答
3

如果您的对象具有合理的赋值语义(和正确的 operator=),那么 *anObject = AnObject() 更有意义,也更容易理解。

于 2009-07-14T10:56:30.203 回答
1

向对象添加类似 Reset() 方法的方法比使用放置新方法要好得多。

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

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

于 2009-07-14T11:00:23.847 回答