【问题标题】:Base constructors inherited to derived class?继承到派生类的基构造函数?
【发布时间】:2017-01-17 17:40:31
【问题描述】:

我一直认为基类的构造函数/析构函数/友元不会被派生类继承。此链接证实了这一点: http://www.geeksforgeeks.org/g-fact-4/.

我还知道我们可以在派生类初始化列表的初始化列表中编写基类构造函数。

话虽如此:我今天试着检查一下我的技能。但是我没能猜到这个程序的输出。

#include<iostream>

class A {
  int x, y;
  public:
    A(int a = 0, int b = 0) : x(a), y(b) {
      std::cout << "A ctor called" << std::endl;
    }
    void print_A() {
      std::cout << "x = " << x << std::endl;
      std::cout << "y = " << y << std::endl;
    }
};

class B : public A {
  int z;
  public:
    // I knew that A member can be initilized like this.
    B(int a = 0, int b = 0, int c = 0) : z(a), A(b, c) {
      std::cout << "C ctor called" << std::endl;
      // I was not aware about that. 
      A(b, c);
    }
    void print_B() {
      std::cout << "z = " << z << std::endl;
    }
};

int main() {
  B b(1, 2, 3);
  b.print_A();
  b.print_B();
}

输出:

A ctor called
C ctor called
A ctor called
x = 2
y = 3
z = 1

几个问题:

  • 如果构造函数/析构函数/友元不是从基类继承的,那么类“B”如何能够在此处访问类“A”的构造函数。

  • 你是怎么得到这个输出的? 'A' 的两个构造函数怎么被调用了。

【问题讨论】:

    标签: c++ constructor


    【解决方案1】:

    你的理解是错误的。这个:

    // I was not aware about that. 
      A(b, c);
    

    不初始化 B 的 A 成员,它(至少在理论上)在构造函数的主体中创建一个临时的、无名的局部变量,有点类似于你说过:

      A a(b, c);
    

    A 的构造函数是一个公共成员,所以任何东西都可以调用它。

    【讨论】:

      【解决方案2】:

      如果构造函数/析构函数/友元不是从基类继承的,那么'B'类怎么能在这里访问'A'类的构造函数?

      “未继承”并不意味着“派生类无法访问”。派生类当然可以引用基构造函数。 B的构造函数做了两次:

      • 第一次访问在初始化列表中
      • 第二次访问是在B的构造函数体中;它会创建一个临时对象

      继承构造函数意味着B 的用户将能够访问B(int, int),这是他们无法做到的*

      这似乎是一个构造函数调用。为什么会创建一个临时对象?

      考虑这种方法:

      void foo(const A& a);
      

      一种常见的方式是这样称呼它:

      A a(1, 2);
      foo(a);
      

      但 C++ 也允许您调用它而无需在单独的行上创建 A

      foo(A(1, 2));
      

      在这种情况下,C++ 创建一个临时对象,并传递foo 对它的引用。当你写

      A(1, 2)
      

      C++ 还通过调用其构造函数为您创建一个临时对象。

      怎么会调用'A'的两个构造函数。

      构造函数被调用两次;这就是你得到输出的原因。

      * C++11 的using 机制可以让你达到very similar to constructor inheritance 的效果,前提是你遵循特定的规则。

      【讨论】:

        【解决方案3】:

        每次调用 A(b, c) 时都会调用构造函数;

        你在线调用它

        B(int a = 0, int b = 0, int c = 0) : z(a), A(b, c) {
        

        你在 b 构造函数中调用它

        // I was not aware about that. 
              A(b, c);
        

        代码按预期工作

        【讨论】:

          【解决方案4】:

          如果构造函数/析构函数/友元不是从基类继承的,那么'B'类怎么能在这里访问'A'类的构造函数。

          你的意思是在这几行?

            // I was not aware about that. 
            A(b, c);
          

          这不是您想象的“访问构造函数”。它只是在构造函数的主体中创建(并立即丢弃)一个匿名临时 A

          你是怎么得到这个输出的? 'A' 的两个构造函数怎么被调用了。

          因为您创建了A 的两个实例:B b 的基类子对象和匿名临时对象。

          这是一个简单的实验来验证这一点,使用以下日志记录:

          // in A
          A(int a = 0, int b = 0) : x(a), y(b) {
            std::cout << "A::A @" << static_cast<void*>(this) << std::endl;
          }
          ~A() {
            std::cout << "A::~A @" << static_cast<void*>(this) << std::endl;
          }
          
          // in B
          B(int a = 0, int b = 0, int c = 0) : z(a), A(b, c) {
            std::cout << "B::B @" << static_cast<void*>(this) << std::endl;
            A(b, c);
          }
          ~B() {
              std::cout << "B::~B @" << static_cast<void*>(this) << std::endl;
          }
          

          我得到类似的输出

          A::A @0xffec7054
          B::B @0xffec7054
          A::A @0xffec704c
          A::~A @0xffec704c
          x = 2
          y = 3
          z = 1
          B::~B @0xffec7054
          A::~A @0xffec7054
          

          看到第二个A::A 的实例地址与第一个不同(所以它是一个不同的对象),后面跟着一个A::~A(因为匿名临时对象立即超出范围)。


          注意事项:

          1. 如果您可以按照最初的建议“调用构造函数”,它看起来像

            auto subobject = static_cast<A*>(this);
            new (subobject) A(b, c);
            

            而且是非常错误的。 bA 子对象在它自己的构造函数完成时完全构造,在 B 的构造函数主体开始之前。你不能只是在同一个空间中重新创建一个新对象,旧对象会发生什么?

            这可能看起来微不足道,但对于具有动态分配资源的对象,这将是一个严重的错误。这是不允许的。

          2. 你写了你的初始化列表: z(a), A(b, c),但应该知道基类子对象将在派生类成员初始化之前构造。也就是说,这两件事将以与您所写相反的顺序发生。这不是(必然)错误,但值得了解。

          【讨论】:

          • 但是为什么,这似乎是一个构造函数调用。为什么会创建一个临时对象。
          • 创建一个对象调用构造函数。这就是如何对象的构造。 A(b,c)A a(b,c) 相同,只是您没有为新对象命名。比较表达式new A(b,c),它也在新分配的内存中构造一个对象(在这种情况下,生成的A*指针通常会保存在一个命名变量中,但对象本身没有名称)。
          猜你喜欢
          • 2015-11-03
          • 2012-12-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-05
          • 1970-01-01
          • 2021-04-20
          • 2016-07-19
          相关资源
          最近更新 更多