"空"构造函数或析构函数是否会与生成的构造函数或析构函数执行相同的操作?

And*_*ong 72 c++ oop constructor destructor class

假设我们有一个(玩具)C++类,如下所示:

class Foo {
    public:
        Foo();
    private:
        int t;
};
Run Code Online (Sandbox Code Playgroud)

由于没有定义析构函数,因此C++编译器应该自动为类创建一个析构函数Foo.如果析构函数不需要清理任何动态分配的内存(也就是说,我们可以合理地依赖编译器给我们的析构函数),那么将定义一个空的析构函数,即.

Foo::~Foo() { }
Run Code Online (Sandbox Code Playgroud)

做与编译器生成的一样的事情?那个空构造函数怎么样 - 也就是说,Foo::Foo() { }

如果存在差异,它们存在于何处?如果没有,一种方法优于另一种方法吗?

Joh*_*itb 116

它会做同样的事情(实质上没什么).但是,如果你没有写它,那就不一样了.因为编写析构函数需要一个可操作的基类析构函数.如果基类析构函数是私有的,或者如果有任何其他原因无法调用它,那么您的程序就会出错.考虑一下

struct A { private: ~A(); };
struct B : A { }; 
Run Code Online (Sandbox Code Playgroud)

没关系,只要您不需要破坏类型B的对象(因此,隐式地使用类型A) - 就像您从未在动态创建的对象上调用delete,或者您从未在其中创建对象第一名.如果这样做,编译器将显示适当的诊断.现在,如果你明确提供一个

struct A { private: ~A(); };
struct B : A { ~B() { /* ... */ } }; 
Run Code Online (Sandbox Code Playgroud)

那个将尝试隐式调用基类的析构函数,并将在定义时间引起诊断~B.

另一个区别在于析构函数的定义和对成员析构函数的隐式调用.考虑这个智能指针成员

struct C;
struct A {
    auto_ptr<C> a;
    A();
};
Run Code Online (Sandbox Code Playgroud)

假设类型的对象C是在.cpp文件中A的构造函数的定义中创建的,该构造函数还包含struct的定义C.现在,如果使用struct A,并且需要销毁A对象,编译器将提供析构函数的隐式定义,就像上面的情况一样.该析构函数还将隐式调用auto_ptr对象的析构函数.这将删除它所持有的指针,指向C对象 - 不知道定义C!这出现在.cpp定义struct A的构造函数的文件中.

这实际上是实现pimpl习语的常见问题.这里的解决方案是添加析构函数并在.cpp文件中提供它的空定义,其中C定义了结构.在它调用其成员的析构函数时,它将知道struct的定义C,并且可以正确地调用它的析构函数.

struct C;
struct A {
    auto_ptr<C> a;
    A();
    ~A(); // defined as ~A() { } in .cpp file, too
};
Run Code Online (Sandbox Code Playgroud)

请注意,boost::shared_ptr没有这个问题:当以某种方式调用其构造函数时,它需要一个完整的类型.

在当前C++中有所不同的另一点是,当你想memset在这样一个用户声明了析构函数的对象上使用和朋友时.这些类型不再是POD(普通旧数据),并且不允许进行位复制.请注意,实际上并不需要这种限制 - 并且下一个C++版本改进了这种情况,因此只要不进行其他更重要的更改,它就允许您仍然对这些类型进行位复制.


既然你问过构造函数:嗯,对于这些,同样的事情也是如此.请注意,构造函数还包含对析构函数的隐式调用.在诸如auto_ptr之类的东西上,这些调用(即使实际上并没有在运行时完成 - 纯粹的可能性在这里已经很重要)会对析构函数造成同样的伤害,并且当构造函数中的某些内容抛出时会发生 - 然后编译器需要调用析构函数的成员.这个答案使用了默认构造函数的隐式定义.

此外,我所说的关于上面的析构函数的可见性和POD也是如此.

初始化有一个重要的区别.如果你把一个用户声明的构造函数,你的类型不再接收成员的值初始化,并且由你的构造函数来完成所需的任何初始化.例:

struct A {
    int a;
};

struct B {
    int b;
    B() { }
};
Run Code Online (Sandbox Code Playgroud)

在这种情况下,以下内容始终为真

assert(A().a == 0);
Run Code Online (Sandbox Code Playgroud)

虽然以下是未定义的行为,因为b从未初始化(您的构造函数省略了).该值可能为零,但也可能是任何其他奇怪的值.尝试从这样的未初始化对象中读取会导致未定义的行为.

assert(B().b == 0);
Run Code Online (Sandbox Code Playgroud)

这也是在使用此语法真newnew A()(注意最后括号-如果他们被省略值初始化没有这样做,而且因为没有用户声明的构造,可以将其初始化,a将未初始化).


Gre*_*osz 18

我知道我在讨论中已经迟到了,但是我的经验表明,与编译器生成的析构函数相比,面对空的析构函数时编译器的行为会有所不同.至少在MSVC++ 8.0(2005)和MSVC++ 9.0(2008)中就是这种情况.

在查看生成的程序集以获取一些使用表达式模板的代码时,我意识到在发布模式下,对my的调用BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)从未被内联.(请不要注意确切的类型和操作员签名).

为了进一步诊断问题,我启用了默认关闭的各种编译器警告.该C4714警告是特别有趣.当标记的函数__forceinline 没有内联时,编译器会发出它.

我启用了C4714警告并且我标记了操作员,__forceinline我可以验证编译器报告它无法内联对操作员的调用.

在文档中描述的原因中,编译器无法内联标记__forceinline为for 的函数:

当-GX/EHs/EHa打开时,函数按值返回可解除对象的函数

这就是我的情况BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs).BinaryVectorExpression由值返回,即使析构函数为空,也会将此返回值视为不可解除的对象.添加throw ()到析构函数并没有帮助编译器,我仍然避免使用异常规范.注释掉空的析构函数让编译器完全内联代码.

外卖是从现在开始,在每个课程中,我都写了注释掉空的析构函数,让人们知道析构函数不会故意做任何事情,就像人们注释掉空的异常规范`/*throw()*/一样表明析构函数不能抛出.

//~Foo() /* throw() */ {}
Run Code Online (Sandbox Code Playgroud)

希望有所帮助.


Fai*_*ali 12

您在类之外定义的空析构函数在大多数情况下具有类似的语义,但不是全部.

具体来说,隐式定义的析构函数
1)是一个内联的公共成员(你的内联不是内联的)
2)被表示为一个简单的析构函数(必须创建可以在联合中的普通类型,你的不能)
3)有一个异常规范(throw) (),你的没有)


Dav*_*ler 8

是的,那个空的析构函数与自动生成的析构函数相同.我总是让编译器自动生成它们; 我认为没有必要明确指定析构函数,除非你需要做一些不寻常的事情:比如虚拟或私有.