C ++构造函数/析构函数继承

时间:2013-01-06 16:46:06

标签: c++ inheritance constructor destructor

编辑:答案摘要

在下文中,B是A的子类。

这是一个术语问题; ctors和dtors是继承的,因为B的ctor / dtor 从A的界面借用。一个类至少有一个构造函数,并且只有一个析构函数。

  • 构造
    • B不会从A;
    • 继承构造函数
    • 除非B的ctor明确地调用一个 A的ctor,否则来自A的默认ctor将在 B的ctor主体之前自动调用(这个想法是A需要初始化)在B创建之前)。
  • 析构
    • B不继承A的dtor;
    • 退出后,B的析构函数会自动调用A的析构函数。

致谢: 我要特别感谢Oli Charlesworth和Kos的答案,我将Kos的答案作为解决方案,因为这是我最了解的答案。


原始帖子

当您在Google上搜索“C ++析构函数继承网站:stackoverflow.com”时,您目前可以找到以下帖子:

  1. Constructor and Destructor Inheritance:两位声誉为30k +的用户说它是继承的,并且它不是
  2. Are virtual destructors inherited?:这里没有提到任何会导致析构函数不被继承的内容
  3. Destructors and inheritance in C++?:评论似乎表明析构函数是继承的
  4. Q1:我在实践中也知道,你不能使用与它的父构造函数相同的原型初始化派生对象而不明确定义派生类的构造函数,这是正确的吗? / p>


    尽管从帖子中可以清楚地看出析构函数似乎是继承的,但我仍然感到困惑的是,拥有32k声望的用户会说它不是。我写了一个小例子,应该澄清每个人的想法:

    #include <cstdio>
    
    /******************************/
    
    // Base class
    struct A
    {
        A() { printf( "\tInstance counter = %d (ctor)\n", ++instance_counter ); }
        ~A() { printf( "\tInstance counter = %d (dtor)\n", --instance_counter ); }
    
        static int instance_counter;
    };
    
    // Inherited class with default ctor/dtor
    class B : public A {};
    
    // Inherited class with defined ctor/dtor
    struct C : public A
    {
        C() { printf("\tC says hi!\n"); }
        ~C() { printf("\tC says bye!\n"); }
    };
    
    /******************************/
    
    // Initialize counter
    int A::instance_counter = 0;
    
    /******************************/
    
    // A few tests
    int main()
    {
        printf("Create A\n"); A a;
        printf("Delete A\n"); a.~A();
    
        printf("Create B\n"); B b;
        printf("Delete B\n"); b.~B();
    
        printf("Create new B stored as A*\n"); A *a_ptr = new B();
        printf("Delete previous pointer\n"); delete a_ptr;
    
        printf("Create C\n"); C c;
        printf("Delete C\n"); c.~C();
    
    }
    

    这是输出(用g ++ 4.4.3编译):

    Create A
        Instance counter = 1 (ctor)
    Delete A
        Instance counter = 0 (dtor)
    Create B
        Instance counter = 1 (ctor)
    Delete B
        Instance counter = 0 (dtor)
    Create new B stored as A*
        Instance counter = 1 (ctor)
    Delete previous pointer
        Instance counter = 0 (dtor)
    Create C
        Instance counter = 1 (ctor)
        C says hi!
    Delete C
        C says bye!
        Instance counter = 0 (dtor)  // We exit main() now
        C says bye! 
        Instance counter = -1 (dtor)
        Instance counter = -2 (dtor)
        Instance counter = -3 (dtor)
    

    Q2:任何认为不是遗传的人都可以解释一下吗?

    Q3:那么当您使用输入调用子类的构造函数时会发生什么?是否也称为超类的“空构造函数”?

7 个答案:

答案 0 :(得分:32)

术语,术语......

好的,“Foo是继承的”是什么意思?我们的意思是,如果类A的对象在其接口中具有Foo,则B的子类A的对象在其Foo中也具有A接口

  • 构造函数不是对象界面的一部分。它们直接属于类。类Bdelete可以提供完全不同的构造函数集。这里没有“被继承”。

    实现细节:每个B的构造函数调用一些A的构造函数。

  • 析构函数确实是每个对象接口的一部分,因为对象的用户负责调用它们(即直接使用{{1}}或通过让对象超出范围而间接调用它们)。 每个对象只有一个析构函数:它自己的析构函数,可以选择是虚函数。它始终是它自己的,它不是遗传的。

    (实现细节:B的析构函数调用A的析构函数。)

所以:基础构造函数和析构函数之间存在连接,但它不像“它们是继承的”。

我希望这能回答你的想法。

答案 1 :(得分:7)

  

Q1:我在实践中也知道,在没有明确定义派生类的构造函数的情况下,你不能使用与它的父构造函数相同的原型来初始化派生对象,这是正确的吗? / p>

除了你在超类中定义了默认构造函数的琐碎案例之外,是的,你是对的。


  

Q2:任何认为不是遗传的人都可以解释一下吗?

这可能是术语定义的问题。虽然很明显虚拟析构函数存在并且“按预期”工作,但我们在C ++标准([class.virtual])中看到:

  

即使析构函数不是继承的,派生类中的析构函数会覆盖声明为虚拟的基类析构函数

(强调我的)


  

Q3:那么当您使用输入调用子类的构造函数时会发生什么?是否也称为超类的“空构造函数”?

如果没有显式调用特定的超类构造函数,那么将调用默认的超类构造函数(假设它是可见的)。

答案 2 :(得分:4)

析构函数继承。如果类没有定义一个,则编译器生成一个。对于微不足道的情况,析构函数只是调用基类的析构函数,这通常意味着它的析构函数(模仿继承)没有明确的代码。但是如果一个类有成员带有析构函数,那么生成的析构函数会在调用基类的析构函数之前为这些成员调用析构函数。这是继承功能不会做的事情。

答案 3 :(得分:3)

继承是什么:重用和扩展现有类而不修改它们的机制,从而在它们之间产生层次关系。

继承几乎就像将对象嵌入到类中一样。

当类继承基类时,基类的构造函数首先是调用,然后是派生类,而析构函数的调用是相反的顺序。

那么为什么调用Base Class Constructor(调用not inherited可能带参数/ default):以保证在执行派生类的构造函数时正确构造基类。

现在调用析构函数(调用不继承):当基础对象超出范围时,析构函数就会自行调用。因此,存在析构函数继承的np问题。

现在您的问题:

ans 1 - 是的,你对第一个问题是正确的。
ans 2 - 因此在对象范围熄灭后调用析构函数不会被继承 &安培; ans 3 - 如果在派生类中您使用参数进行调用,则只调用该构造函数,并且不会调用其他构造函数。
没有任何意义可以在对象创建时调用相同对象的2个构造函数,如 构造函数在创建对象时调用。它准备使用新对象。因此没有使用不同构造函数两次准备对象的逻辑。

答案 4 :(得分:3)

从技术上讲,析构函数是继承的。但在正常情况下,继承的析构函数不直接用于派生类;它们被调用,因为派生类自己的析构函数调用它们来销毁它自己的“基类子对象”,作为销毁较大对象的一个​​步骤。在直接在派生对象上使用基类析构函数的特殊情况下,很难避免未定义的行为。

此示例直接来自C ++标准版(12.4p12)。

struct B {
  virtual ~B() { }
};
struct D : B {
  ~D() { }
};

D D_object;
typedef B B_alias;
B* B_ptr = &D_object;

void f() {
  D_object.B::~B();              // calls B's destructor
  B_ptr->~B();                   // calls D's destructor
  B_ptr->~B_alias();             // calls D's destructor
  B_ptr->B_alias::~B();          // calls B's destructor
  B_ptr->B_alias::~B_alias();    // calls B's destructor
}

如果~B不是D的继承成员,则f中的第一个语句将是格式错误的。事实上,它是合法的C ++,虽然非常危险。

答案 5 :(得分:1)

在您的示例中,您显式调用了析构函数。这是合法的(显然,自编译和运行以来),但几乎总是不正确。

对于使用new创建的动态分配对象,当使用delete删除对象时,将运行析构函数。

对于静态分配的对象(仅通过在函数范围内声明对象来创建),析构函数在对象的作用域消失时运行。也就是说,当main()退出时,将运行对象的析构函数。但是你已经通过手动调用它们来运行这些对象的析构函数了!这就是为什么您的示例的输出显示计数减少到-3 ...您已经运行了abc两次的析构函数。

这是相同的代码,注释显示何时自动运行析构函数:

int main()
{
    printf("Create A\n"); A a;
    printf("Delete A\n"); a.~A();

    printf("Create B\n"); B b;
    printf("Delete B\n"); b.~B();

    printf("Create new B stored as A*\n"); A *a_ptr = new B();
    printf("Delete previous pointer\n");
    delete a_ptr;   // Implicitly calls destructor for a_ptr.  a_ptr is class B,
       // so it would call a_ptr->~B() if it existed. Because B is an A, after
       // its destructor is called, it calls the superclass's destructor,
       // a_ptr->~A().

    printf("Create C\n"); C c;
    printf("Delete C\n"); c.~C();
}
// Function exits here at the close brace, so anything declared in its scope is
// deallocated from the stack and their destructors run.
// First `c` is destroyed, which calls c.~C(), then because C is a subclass of A
// calls c.~B() (which doesn't exist, so a blank implementation is used), then
// because B is a subclass of A calls c.~A().  This decrements the counter, but
// the count is wrong because you already manually called c.~C(), which you
// ordinarily shouldn't have done.
// Then `b` is destroyed, in a similar manner.  Now the count is off by 2,
// because you had already called b.~B().
// Lastly `a` is destroyed, just as above.  And again, because you had already
// called a.~A(), the count is now off by 3.

答案 6 :(得分:0)

I would want to express my thoughts. Creating any object is done in two stages:

1。为对象分配内存区域。

  1. 初始化此内存区域。

    对象的构造函数是类(针对此对象)的函数(方法),该函数初始化内存的分配区域并自动调用。 继承将一个类的对象嵌入到另一类的对象中。有戏剧与“这”“在盖子下”的poiners。 “ this”隐式地传递给类的方法。

    完成代码“ B b”后会发生什么。首先,为对象b分配了内存区域。类B具有自己的默认构造函数B(),该构造函数会自动调用以初始化此内存。 B()是函数,因此创建堆栈框架以工作一个。该构造函数的地址为b(隐含)。但是A的对象必须嵌入到对象b中。 A的对象没有名称。 B的构造函数知道A的noname嵌入对象也必须创建(因此,编译器C ++可以工作)。因此,将在类B的构造函数中调用用于初始化类A的noname嵌入对象的类A的构造函数。调用新的堆栈框架,并初始化noname对象。之后,关闭堆栈框架,并完成我们的B类对象b。我认为b的地址和noname对象重合。

    析构函数也是类的方法。当我们调用〜B()时,b不被破坏。销毁器是销毁对象时自动调用的功能。但这并不意味着当我们调用析构函数时,对象必须被销毁。如果调用了B的析构函数,则会为其中一个创建堆栈帧。 B的默认解释器知道类A的无名嵌入对象(因此,编译器C ++可以运行)。因此,析构函数称为A的析构函数。