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

C++ Constructor/Destructor inheritance

本文关键字:继承 析构函数 构造函数 C++      更新时间:2023-10-16

编辑:答案摘要

在下文中,B是a.的一个子类

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

  • 构造函数
    • B没有从A继承构造函数
    • 除非B的ctor显式地调用A的ctor中的一个,否则A的默认ctor将在B的ctorbody之前自动调用[其思想是,在创建B之前,A需要初始化]
  • 析构函数
    • B没有继承A的数据
    • 退出后,B的析构函数将自动调用A的析构因子

鸣谢:我要特别感谢Oli Charlesworth和Kos的回答,我把Kos的答案作为解决方案,因为这是我最理解的答案。


原始帖子

当你在谷歌上搜索"C++析构函数继承网站:stackoverflow.com"时,你会发现以下帖子:

  1. 构造函数和析构函数继承:两个拥有30k以上信誉的用户说它是继承的,而不是
  2. 虚拟析构函数是继承的吗?:这里没有提到任何可能指向析构函数没有被继承的内容
  3. C++中的析构函数和继承?:注释似乎表明析构函数是继承的

Q1:我从实践中还知道,如果不明确定义派生类的构造函数,就无法用与其父构造函数相同的原型初始化派生对象,这是正确的吗?


尽管从帖子中可以清楚地看出,析构函数似乎是继承的,但我仍然对一个拥有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 An"); A a;
    printf("Delete An"); a.~A();
    printf("Create Bn"); B b;
    printf("Delete Bn"); b.~B();
    printf("Create new B stored as A*n"); A *a_ptr = new B();
    printf("Delete previous pointern"); delete a_ptr;
    printf("Create Cn"); C c;
    printf("Delete Cn"); 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:那么,当您用输入调用子类的构造函数时,会发生什么呢?是否也调用了超类的"空构造函数"?

术语、术语。。。

好吧,我们所说的"福是继承的"是什么意思?我们的意思是,如果类A的对象在其接口中有Foo,那么作为A的子类的类B的对象在它的接口中也有Foo

  • 构造函数不是对象接口的一部分。他们直接属于阶级。类AB可以提供完全不同的构造函数集合。这里没有"被继承"。

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

  • 析构函数确实是每个对象接口的一部分,因为对象的用户负责调用它们(即直接使用delete或通过让对象超出范围间接调用(每个对象只有一个析构函数:它自己的析构函数,也可以是虚拟析构函数。它永远是自己的,不是继承的。

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

所以:在基构造函数、派生构造函数和析构函数之间存在联系,但这并不是"它们是继承的"。

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

Q1:我从实践中还知道,如果不为派生类显式定义构造函数,就无法用与其父构造函数相同的原型初始化派生对象,这是正确的吗?

除了在超类中定义默认构造函数的琐碎情况之外,是的,你是正确的。


Q2:任何认为它没有遗传的人能解释一下吗?

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

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

(重点矿井(


Q3:那么,当您用输入调用子类的构造函数时,会发生什么呢?是否也调用了超类的"空构造函数"?

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

析构函数是而不是继承的。如果一个类没有定义一个,编译器会生成一个。对于析构函数只调用基类的析构函数的琐碎情况,这通常意味着其析构函数(模仿继承(没有显式代码。但是,如果一个类有带析构函数的成员,则生成的析构函数会在调用基类析构函数之前为这些成员调用析构函数。这是继承函数无法做到的。

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

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

当类继承基类时,基类的构造函数被首先调用,然后是派生类的构造函数,析构函数的调用

为什么调用基类构造函数(调用而非继承可能带有参数/默认值(:以确保在执行派生类的构造函数时正确构造基类。

现在调用析构函数(调用not inherit(:当基对象超出范围时,析构函数会被自己调用。因此存在析构函数继承的np问题。

现在您的问题:

ans 1-是的,你对第一个问题是正确的
ans 2-所以在对象的作用域结束后,析构函数被调用而不是继承的
&ans3-如果在派生类中,您使用参数进行调用,则只会调用该构造函数,而不会调用其他构造函数。
在创建对象时调用同一对象的2个构造函数是没有问题的,因为构造函数在创建对象时调用。它准备新对象以供使用。因此,不存在使用不同构造函数两次准备对象的逻辑。

从技术上讲,析构函数是继承的。但在正常情况下,继承的析构函数不会直接用于派生类;它们被调用是因为派生类自己的析构函数调用它们是为了销毁自己的"基类子对象",作为销毁较大对象的步骤。在不寻常的情况下,直接对派生对象使用基类析构函数,很难避免Undefined Behavior。

这个例子直接来自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++,尽管极其危险。

在您的示例中,您显式地调用析构函数。这是合法的(显然,因为它是编译和运行的(,但几乎总是不正确的。

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

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

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

int main()
{
    printf("Create An"); A a;
    printf("Delete An"); a.~A();
    printf("Create Bn"); B b;
    printf("Delete Bn"); b.~B();
    printf("Create new B stored as A*n"); A *a_ptr = new B();
    printf("Delete previous pointern");
    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 Cn"); C c;
    printf("Delete Cn"); 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.
I would want to express my thoughts. Creating any object is done in two stages:

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

  1. 正在初始化此内存区域。

    对象的构造函数是类(针对该对象(的函数(方法(,它初始化分配的内存区域并自动调用。继承是将一个类的对象嵌入到另一个类中。有一些戏剧是用"这个"盖下"来形容的。"this"被隐式传递给类的方法。

    当代码"B B"完成时会发生什么。首先,内存区域被分配给对象b。类b有自己的默认构造函数b((,它被自动调用来初始化这个内存。B((是函数,因此创建堆栈帧是为了处理一个。此构造函数的地址为b(implicity(。但是A的对象必须嵌入到对象b中。A的对象没有名称。B的构造函数知道也必须创建A的非名称嵌入对象(因此编译器C++可以工作(。因此,在B的构造函数中调用了用于初始化类A的非名称嵌入对象的类A的构造函数。调用了新的堆栈帧,并且正在初始化非名称对象。在那之后,堆栈帧被关闭,并且我们的类b的对象b已经完成。我认为b的地址和非名字对象是一致的。

    析构函数也是类的方法。当我们调用~B((时,B不会被破坏。析构函数是在对象被销毁时自动调用的函数。但这并不意味着当我们调用析构函数时,对象必须被销毁。如果调用了B的析构函数,则为其创建堆栈帧。B的默认构造函数知道类A的非名称嵌入对象(因此编译器C++可以工作(。因此析构函数调用A.的析构函数