像普通方法一样调用构造函数和析构函数的注意事项和风险?

Caveats and risks of calling a constructor and destructor like common methods?

我的程序中有一点需要重置某个对象的状态"to factory defaults"。该任务归结为执行析构函数和构造函数中编写的所有内容。我可以删除并重新创建该对象——但我可以将析构函数和构造函数作为普通对象调用吗? (特别是,我不想将更新后的指针重新分配给新实例,因为它在程序其他地方的副本中徘徊)。

  MyClass {
  public:
        MyClass();
        ~MyClass();

  ...      
  }


  void reinit(MyClass* instance)
  {
        instance->~MyClass();
        instance->MyClass();
  }

我可以这样做吗?如果是这样,是否有任何风险、注意事项或我需要记住的事情?

Can I do this? If so, are there any risks, caveats, things I need to remember?

不,你不能这样做。除了析构函数调用在技术上是可行的,它只是未定义的行为。


假设您已经实现了 class correctly 的赋值运算符,您可以只写:

void reinit(MyClass* instance) {
    *instance = MyClass();
}

如果您的赋值运算符和构造函数编写正确,您应该能够将其实现为:

void reinit(MyClass* instance)
{
    *instance = MyClass();
}

如果您的赋值运算符和构造函数编写不正确,请修复它们。

将重新初始化实现为先销毁再构造的警告是,如果构造函数失败并抛出异常,对象将被销毁两次,而不会在第一次和第二次销毁之间再次构造(一次由您的手册销毁,一次是在其所有者超出范围时发生的自动销毁)。这有未定义的行为。

您应该使用智能指针并依靠移动语义来获得您想要的行为。

auto classObj = std::make_unique<MyClass>();

这会创建一个包装指针来为您处理动态内存。假设您已准备好将 classObj 重置为出厂默认设置,您只需要:

classObj = std::make_unique<MyClass>();

这个"move-assignment"操作会调用MyClass的析构函数,然后重新分配classObj智能指针指向一个新构造的MyClass实例。起泡沫,冲洗,必要时重复。换句话说,您不需要 reinit 函数。然后当classObj被销毁时,它的内存就被清理了。

您可以尝试使用 placement new 表达式

new (&instance) MyClass()

instance->MyClass();不合法,一定会编译出错。

instance->~MyClass();是可以的。这会做以下两件事之一:

  • 没有,如果 MyClass 有一个平凡的析构函数
  • 运行析构函数中的代码并结束对象的生命周期,否则。

如果在对象的生命周期结束后使用它,则会导致未定义的行为。

很少写 instance->~MyClass();,除非您首先使用 placement new 创建了对象,或者您将重新创建对象展示位置新。

如果您不知道,placement new 会在您已经分配存储空间时创建一个对象。例如这是合法的:

{
    std::string s("hello");
    s.~basic_string();
    new(&s) std::string("goodbye");

    std::cout << s << '\n';
}

你可以使用 placement-new:

void reinit(MyClass* instance)
{
    instance->~MyClass();
    new(instance) MyClass();
}

所有指针仍然有效。

或者作为成员函数:

void MyClass::reinit()
{
    ~MyClass();
    new(this) MyClass();
}

这应该小心使用,参见 http://www.gotw.ca/gotw/023.htm,这是关于用这个技巧实现赋值运算符的,但这里也有一些要点:

  • 构造函数不应该抛出
  • MyClass 不应用作基础 class
  • 它会干扰 RAII,(但这可能是想要的)

.