71

编辑:答案摘要

下面,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. 构造函数和析构函数继承:两个信誉超过 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 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:那么当你调用带有输入的子类的构造函数时会发生什么?是否也调用了超类的“空构造函数”?

4

7 回答 7

42

术语,术语...

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

  • 构造函数不是对象接口的一部分。它们直接属于类。类A,并且B可能提供完全不同的构造函数集。这里没有“被继承”。

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

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

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

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

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

于 2013-01-06T17:07:42.813 回答
7

Q1:我从实践中也知道,如果不为派生类显式定义构造函数,就不能使用与父构造函数相同的原型来初始化派生对象,对吗?

除了您在超类中定义默认构造函数的微不足道的情况外,是的,您是正确的。


Q2:有人认为它不是遗传的,请解释一下吗?

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

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

(强调我的)


Q3:那么当你调用带有输入的子类的构造函数时会发生什么?是否也调用了超类的“空构造函数”?

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

于 2013-01-06T16:53:02.200 回答
4

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

此示例直接来自 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,则 in 中的第一条语句f格式错误。事实上,它是合法的 C++,尽管极其危险。

于 2013-01-06T21:21:15.620 回答
4

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

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

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

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

现在调用析构函数(调用不继承):当基础对象超出范围时,析构函数被自己调用。所以存在析构函数继承的 np 问题。

现在你的问题:

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

于 2013-01-06T17:18:35.577 回答
4

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

于 2013-01-06T17:11:03.533 回答
2

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

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

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

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

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.
于 2014-03-28T18:04:28.733 回答
0
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(隐式)。但是 A 的对象必须嵌入到对象 b 中。A 的对象没有名称。B 的构造函数知道 A 的无名嵌入对象也必须创建(因此编译器 C++ 工作)。因此,在 B 的构造函数中调用了用于初始化 A 类的无名嵌入对象的 A 类的构造函数。调用了新的堆栈帧并正在初始化无名对象。之后,堆栈帧被关闭,我们的 B 类对象 b 已经完成。

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

于 2018-11-21T20:41:59.907 回答