【问题标题】:Why can't constructors be explicitly called while destructors can?为什么不能显式调用构造函数而析构函数可以?
【发布时间】:2017-12-21 20:43:36
【问题描述】:

在下面的 C++ 代码中,我可以显式调用析构函数,但不能显式调用构造函数。这是为什么?在 dtor 的情况下,不是更明确的 ctor 调用 expressiveunified 吗?

class X { };

int main() {
  X* x = (X*)::operator new(sizeof(X));
  new (x) X;  // option #1: OK
  x->X();     // option #2: ERROR

  x->~X();
  ::operator delete(x);
}

【问题讨论】:

  • 看起来你正在调用一个未初始化对象的成员函数,即 UB。
  • 'new (x) X' 是什么意思?为什么会编译?它应该是“新的 X(x)”吗?
  • @patatahooligan:有关详细信息,请参阅新展示位置。它只是构造对象,即不分配内存。
  • @Ron:它分配sizeof(X)字节的内存,返回的指向空的指针只是转换为指向X的指针。
  • 你可以认为placement-new是一个显式的构造函数调用

标签: c++ constructor destructor placement-new explicit-destructor-call


【解决方案1】:

因为在构造函数启动之前,该地址没有X 类型的对象。因此,将 x 取消引用为 X 类型或访问它的成员/方法将是未定义的行为。

所以x->X();(假设语法)和x->~X()之间的主要区别在于,在第二种情况下,您有一个可以调用(特殊)成员(例如析构函数)的对象,而在第一种情况下, 还没有可以调用方法的对象(甚至是特殊方法 - 构造函数)。

您可以争辩说这条规则可能有一个例外,但最终这将是语法偏好的问题,在这两种情况下您都会有不一致的地方。使用当前语法,对构造函数的调用看起来不像对构造函数的调用,在您建议的语法中,与析构函数调用将是对称的,但是当您可以取消引用/访问对象的方法时,规则不一致。实际上必须有一个例外,允许在还不是对象的东西上调用方法。然后你必须在标准的字母中严格定义还不是对象的东西

【讨论】:

  • 听起来很合理。抱歉,我在学习安置新东西时没有想到这么基本的问题。
  • @DanielLangr 不要道歉。 质疑一切!如果它是主题,你可以在这里提问:)
  • 感谢您对可能的异常的澄清。我也在考虑这个问题。
  • 补充注解:从概念上讲,构造函数实际上是一个类方法,而析构函数实际上是一个实例方法。
  • @Matthias 如果“类方法”是指静态方法,而“实例方法”是指非静态方法,那么你是不正确的。构造函数是一个“特殊成员函数”(§12 特殊成员函数 [special])。即使没有标准,您也可以在构造函数中访问非静态成员,因此您可以很容易地看到构造函数是非静态成员。
【解决方案2】:

这是先有鸡还是先有蛋的问题。

您可以显式调用析构函数,就像它们是成员函数一样,因为对象的实例已经存在。

你不能对构造函数做同样的事情,因为你要调用它的实例需要存在,并且被构造函数完全初始化。

唯一的例外是当您为对象分配内存但尚未初始化实例时(即实例的内存在那里,但尚未初始化为实际实例)。因此,您需要调用构造函数。这是放置new(您在“选项1”注释下显示的语法)有用的情况。但是,这不是您对实例执行的成员调用,因为在进行该调用之前该实例不可用。

【讨论】:

  • @aschepler 我的意思是它是一个初始化实例的例外,而不是足以容纳实例的内存块。我进行了编辑以更详细地解释这一点。
  • "析构函数...好像它们是成员函数" 析构函数不是成员函数?
  • @curiousguy 析构函数与成员函数共享大量特性,但编译器提供的特殊处理将它们与常规成员函数区分开来。这就是为什么我不愿意在没有进一步限定的情况下将析构函数称为“成员函数”。
  • 构造函数、赋值运算符等析构函数可以隐式生成。它们被自动调用。他们有特殊的规则:例外。它们在许多方面都受到特殊对待,但它们并不是唯一受到特殊对待的成员函数。
【解决方案3】:

您可以使用placement new 在任意位置构造对象。

new() 调用可以覆盖参数;放置构造函数采用void* 或指向类型的指针。 new() 函数总是带一个 size_t 的参数,即 sizeof() 类型;这通常只被全局新函数使用

写内存池时使用placement构造函数和explicit destructor。

例如(凭记忆!)

class MyClass
{
    public:
       inline new(size_t size, MyClass *ptr) { return ptr; };
};

这样使用

{
    MyClass *x = ...;
    MyClass *y = new (x) MyClass(construct parameters);
    x->~MyClass();
}

编辑以纠正@Ben-Voigt 指出的错误

【讨论】:

  • 没有“放置构造函数”之类的东西。 Placement-new 表达式导致调用普通构造函数之一(通过重载决议选择)。
  • 我认为您在 new 之前缺少关键字 operatorinline 在这个位置也是没用的。如果您不做任何与默认操作不同的操作,为什么要重载放置 new 操作符?
  • 这解释了什么是新展示位置。然而,它并没有以任何方式回答这个问题。
猜你喜欢
  • 2021-04-01
  • 2014-04-15
  • 2011-11-05
  • 2015-06-10
  • 2012-06-28
  • 1970-01-01
  • 1970-01-01
  • 2020-09-06
相关资源
最近更新 更多