首页 文章

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

提问于
浏览
51

EDIT : Summary of answers

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

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

  • Constructors

  • B不从A继承构造函数;

  • 除非B的ctor明确地调用A的ctor之一,否则A的默认ctor将在B的ctor主体之前自动调用(这个想法是A需要在B创建之前初始化) .

  • Destructors

  • B不继承A的dtor;

  • 退出后,B 's destructor will automatically call A' s析构函数 .

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


ORIGINAL POST

当您在Google上搜索“C析构函数继承站点:stackoverflow.com”时,您当前会发现以下帖子:

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: 那么当您使用输入调用子类的构造函数时会发生什么?是否也调用了超类的"empty constructor"?

7 回答

  • 7

    术语,术语......

    好的,"Foo is inherited"是什么意思?我们的意思是如果类 A 的对象在其接口中具有 Foo ,那么 B 的子类 A 的对象在其接口中也具有 Foo .

    • Constructors aren 't a part of objects'界面 . 它们直接属于类 . 类 AB 可以提供完全不同的构造函数集 . 这里没有"being inherited" .

    (实现细节:每个B 's constructors calls some A'的构造函数 . )

    • Destructors 确实是每个对象的一部分's interface, since the object'用户负责调用它们(即直接使用 delete 或通过让对象超出范围而间接) . Each object has exactly one destructor :它自己的析构函数,可以选择是虚拟的析构函数 . 它始终是它自己的,它不是遗传的 .

    (实现细节:B 's destructor calls A' s析构函数 . )

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

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

  • 31

    Q1:我在实践中也知道的是,如果没有显式定义派生类的构造函数,你不能使用与它的父构造函数相同的原型初始化派生对象,这是正确的吗?

    除了在超类中定义了默认构造函数的简单情况之外,是的,你是对的 .


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

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

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

    (强调我的)


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

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

  • 4

    析构函数是 not 继承的 . 如果一个类没有定义一个,则编译器 generates 一个 . 对于微不足道的情况,析构函数只是调用基类' destructor, and often that means that there is no explicit code for its destructor (which imitates inheritance). But if a class has members with destructors, the generated destructor calls destructors for those members before calling the base class'析构函数 . 这是继承功能不会做的事情 .

  • 3

    Inheritance is what : 重用和扩展现有类而不修改它们的机制,从而产生层次结构他们之间的关系 .

    Inheritance 几乎就像将对象嵌入到类中一样 .

    当类继承基类时,基类的构造函数首先是 called ,然后派生类's ,and the destructor' s call 的顺序相反 .

    So Why Base Class Constructor is called (called not inherited may be with parameters/default) : 保证在执行派生类的构造函数时正确构造基类 .

    Now Calling of Destructor (calling not inherit) : 当基础对象超出范围时,析构函数就会自行调用 . 因此,存在析构函数继承的np问题 .

    now your questions:

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

  • 1

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

    这个例子直接来自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,虽然非常危险 .

  • 3

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

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

    对于静态分配的对象(仅通过在函数范围内声明对象来创建),析构函数在对象的作用域消失时运行 . 也就是说,当 main() 退出时,对象' destructors will be run. But you' ve已经通过手动调用它们来运行这些对象的析构函数!这就是为什么你的例子运行 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.
    
  • 0
    I would want to express my thoughts. Creating any object is done in two stages:
    

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

    • 初始化此内存区域 .

    对象的构造函数是类(对于此对象)的函数(方法),它初始化已分配的内存区域并自动调用 . 继承是将一个类的对象嵌入到其他类的对象中 . 有游戏的玩家“这个”“盖下” . “this”隐含地传递给类的方法 .

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

    析构函数也是类的方法 . 当我们调用~B()时,b不会被破坏 . 析构函数是在对象被销毁时被称为avtomatically的函数 . 但这并不意味着当我们调用析构函数时,必须销毁对象 . 如果调用B的析构函数,则为其中一个创建堆栈帧 . B的默认析构函数知道类A的noname嵌入对象(因此编译器C工作) . 因此析构函数调用A的析构函数 .

相关问题