一个“空”构造函数或析构函数是否与生成的函数做同样的事情? [英] Will an 'empty' constructor or destructor do the same thing as the generated one?

查看:131
本文介绍了一个“空”构造函数或析构函数是否与生成的函数做同样的事情?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

  Foo ::〜Foo(){} 

做同样的事情编译器生成的?如果一个空的构造函数 - 即 Foo :: Foo(){}



差异,他们在哪里存在?如果没有,是一种优先于另一种方法?

解决方案

它会做同样的事情但它不一样,如果你没有写。因为编写析构函数需要一个工作的基类析构函数。如果基类析构函数是私有的或者如果有任何其他原因它不能被调用,那么你的程序是有错误的。考虑这个

  struct A {private:〜A }; 
struct B:A {};

没关系,只要你不需要破坏类型B的对象因此,隐式类型A) - 如果你从来没有调用删除动态创建的对象,或者你从来没有创建它的对象。如果这样做,那么编译器将显示相应的诊断。现在如果你明确提供一个

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

这将尝试隐式调用基类的析构函数,在定义时间〜B



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

  struct C; 
struct A {
auto_ptr< C>一个;
A();
};

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



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

  struct C; 
struct A {
auto_ptr< C>一个;
A();
〜A(); //定义为〜A(){}在.cpp文件中,太
};

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



另一个在当前C ++中有所不同的地方是当你想使用 memset 对象具有用户声明的析构函数。这种类型不再是POD(普通旧数据),并且这些类型不允许进行位复制。注意,这个限制并不是真正需要的 - 下一个C ++版本改进了这种情况,所以它允许你仍然位复制这样的类型,只要没有做其他更重要的更改。






因为你要求构造函数:嗯,因为这些都是真的。注意,构造函数还包含对析构函数的隐式调用。在auto_ptr这样的事情上,这些调用(即使在运行时实际上并没有完成,纯可能性在这里已经很重要)将会对析构函数产生同样的危害,并且当构造函数中的某些东西抛出时,编译器需要调用析构函数的成员。 这个答案让一些使用默认构造函数的隐式定义。



此外,对于可见性和PODness,我说的上面的析构函数也是如此。



有关初始化的一个重要区别。如果你放一个用户声明的构造函数,你的类型不再接收成员的值初始化,它是由你的构造函数做任何所需的初始化。示例:

  struct A {
int a;
};

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

在这种情况下,以下内容总是真实的

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

以下是未定义的行为,因为 b 从来没有初始化(你的构造函数省略了)。该值可以是零,但也可以是任何其他奇怪的值。尝试从这样的未初始化对象读取导致未定义的行为。

  assert(B()。b == 0); 

new ,如 new A()(注意括号在末尾 - 如果它们被忽略值初始化没有完成,并且因为没有用户声明的构造函数可以初始化它, a 将保留未初始化)。


Suppose we have a (toy) C++ class such as the following:

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

Since no destructor is defined, a C++ compiler should create one automatically for class Foo. If the destructor does not need to clean up any dynamically allocated memory (that is, we could reasonably rely on the destructor the compiler gives us), will defining an empty destructor, ie.

Foo::~Foo() { }

do the same thing as the compiler-generated one? What about an empty constructor -- that is, Foo::Foo() { }?

If there are differences, where do they exist? If not, is one method preferred over the other?

解决方案

It will do the same thing (nothing, in essence). But it's not the same as if you didn't write it. Because writing the destructor will require a working base-class destructor. If the base class destructor is private or if there is any other reason it can't be invoked, then your program is faulty. Consider this

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

That is OK, as long as your don't require to destruct an object of type B (and thus, implicitly of type A) - like if you never call delete on a dynamically created object, or you never create an object of it in the first place. If you do, then the compiler will display an appropriate diagnostic. Now if you provide one explicitly

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

That one will try to implicitly call the destructor of the base-class, and will cause a diagnostic already at definition time of ~B.

There is another difference that centers around the definition of the destructor and implicit calls to member destructors. Consider this smart pointer member

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

Let's assume the object of type C is created in the definition of A's constructor in the .cpp file, which also contains the definition of struct C. Now, if you use struct A, and require destruction of an A object, the compiler will provide an implicit definition of the destructor, just like in the case above. That destructor will also implicitly call the destructor of the auto_ptr object. And that will delete the pointer it holds, that points to the C object - without knowing the definition of C! That appeared in the .cpp file where struct A's constructor is defined.

This actually is a common problem in implementing the pimpl idiom. The solution here is to add a destructor and provide an empty definition of it in the .cpp file, where the struct C is defined. At the time it invokes the destructor of its member, it will then know the definition of struct C, and can correctly call its destructor.

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

Note that boost::shared_ptr does not have that problem: It instead requires a complete type when its constructor is invoked in certain ways.

Another point where it makes a difference in current C++ is when you want to use memset and friends on such an object that has a user declared destructor. Such types are not PODs anymore (plain old data), and these are not allowed to be bit-copied. Note that this restriction isn't really needed - and the next C++ version has improved the situation on this, so that it allows you to still bit-copy such types, as long as other more important changes are not made.


Since you asked for constructors: Well, for these much the same things are true. Note that constructors also contain implicit calls to destructors. On things like auto_ptr, these calls (even if not actually done at runtime - the pure possibility already matters here) will do the same harm as for destructors, and happen when something in the constructor throws - the compiler is then required to call the destructor of the members. This answer makes some use of implicit definition of default constructors.

Also, the same is true for visibility and PODness that i said about the destructor above.

There is one important difference regarding initialization. If you put a user declared constructor, your type does not receive value initialization of members anymore, and it is up to your constructor to do any initialization that's needed. Example:

struct A {
    int a;
};

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

In this case, the following is always true

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

While the following is undefined behavior, because b was never initialized (your constructor omitted that). The value may be zero, but may aswell be any other weird value. Trying to read from such an uninitialized object causes undefined behavior.

assert(B().b == 0);

This is also true for using this syntax in new, like new A() (note the parentheses at the end - if they are omitted value initialization is not done, and since there is no user declared constructor that could initialize it, a will be left uninitialized).

这篇关于一个“空”构造函数或析构函数是否与生成的函数做同样的事情?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆