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

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

本文关键字:调用 析构函数 构造函数 注意事项 一样 方法      更新时间:2023-10-16

在我的程序中有一个点,某个对象的状态需要重置为"出厂默认值"。该任务归结为完成析构函数和构造函数中写入的所有内容。我可以删除和重新创建对象,但是我可以像普通对象一样调用析构函数和构造函数吗?(特别是,我不想重新分配更新后的指针到新实例,因为它在程序的其他地方的副本中徘徊)。

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

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

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

如果你的赋值操作符和构造函数写得正确,你应该能够实现它:

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

如果赋值操作符和构造函数写得不正确,修复它们。

将重新初始化实现为销毁后再构造的警告是,如果构造函数失败并抛出异常,对象将被销毁两次,而不会在第一次和第二次销毁之间再次构造(一次是通过手动销毁,一次是通过其所有者超出作用域时发生的自动销毁)。

可以使用place -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不应该用作基类
  • 它干扰了RAII,(但这可能是需要的)

感谢Fred Larson。

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

不,你不能这么做。此外,从技术上讲,析构函数调用是可能的,它将只是未定义的行为。


假设你已经正确地实现了类的赋值操作符,你可以这样写:

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

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

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

创建一个包装指针,为您处理动态内存。假设您准备将classObj重置为出厂默认值,您所需要的是:

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

这个"移动赋值"操作将调用MyClass的析构函数,然后将classObj智能指针重新赋值到指向新构造的MyClass实例。起泡,冲洗,必要时重复。换句话说,您不需要reinit函数。然后,当classObj被销毁时,它的内存将被清理。

instance->MyClass();是非法的,你必须得到一个编译错误。

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

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

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

很少写instance->~MyClass();,除非你首先用位置new创建对象,或者你打算用new位置重新创建对象。

如果你不知道,位置new创建一个对象时,你已经得到了存储分配。例如,这是合法的:

{
    std::string s("hello");
    s.~basic_string();
    new(&s) std::string("goodbye");
    std::cout << s << 'n';
}

您可以尝试使用位置new expression

new (&instance) MyClass()