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

StackOverflow https://stackoverflow.com/questions/1025313

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

class Foo {
    public:
        Foo();
    private:
        int t;
};

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

Foo::~Foo() { }

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

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

有帮助吗?

解决方案

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

struct A { private: ~A(); };
struct B : A { }; 

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

struct A { private: ~A(); };
struct B : A { ~B() { /* ... */ } }; 

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

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

struct C;
struct A {
    auto_ptr<C> a;
    A();
};

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

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

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

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

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


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

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

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

struct A {
    int a;
};

struct B {
    int b;
    B() { }
};

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

assert(A().a == 0);

虽然以下是未定义的行为,因为 b 从未初始化(构造函数省略了)。该

其他提示

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

当查看生成的程序集中某些使用表达式模板的代码时,我意识到在发布模式下,调用我的 BinaryVectorExpression运算符+(const Vector&amp; lhs,const Vector&amp; rhs)从来没有内联。 (请不要注意确切的类型和操作员签名)。

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

我启用了C4714警告,并使用 __ forceinline 标记了操作符,我可以验证编译器报告它无法内联对操作员的调用。

在文档中描述的原因中,编译器无法内联用 __ forceinline 标记的函数:

  

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

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

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

//~Foo() /* throw() */ {}

希望有所帮助。

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

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

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

我同意大卫的意见,但我认为定义虚拟析构函数通常是一种好习惯,即

virtual ~Foo() { }
错过虚拟析构函数会导致内存泄漏,因为从你的Foo类继承的人可能没有注意到他们的析构函数永远不会被调用!!

我会说最好把空的声明,它告诉任何未来的维护者它不是一个疏忽,你真的打算使用默认声明。

空定义很好,因为可以引用定义

virtual ~GameManager() { };
空声明在外观上看似相似
virtual ~GameManager();
但是邀请可怕的没有定义虚拟析构函数错误< *>

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top