控制派生类中的基础构造函数,潜在的双初始化

时间:2016-08-29 16:46:33

标签: c++ c++11 constructor virtual-method

关于this question and the answer to it似乎确实存在异常,但它为我提出了更多问题而不是回答它们。考虑一下:

#include <iostream>
using namespace std;
struct base {
  virtual void test() {cout << "base::test" << endl;}
  base() {test();}
  virtual ~base() {}
};
struct derived : base {
  virtual void test() {cout << "derived::test" << endl;}
  derived() : base() {}
  ~derived() {}
};
int main() {
  derived d;
  return 0;
}

我天真地认为这只会打印两条消息的 。它实际上打印两者 - 首先是派生的基本版本。这在-O0-O3设置上的行为相同,因此,就我所知,它不是优化或缺乏优化。

我是否理解在base构造函数中调用derived(或更高/更早的类&#39;构造函数)不会阻止默认的base构造函数(或其他)被事先打电话?

也就是说,构建derived对象时上述代码段中的序列是:base()然后derived()再次在base()内?

我知道修改vtable只是为了调用base::base(),回到调用derived::derived()之前的状态是没有意义的,只是为了调用一个不同的构造函数。我只能猜测与vtable相关的东西是硬编码到构造函数链中的,并且调用前面的构造函数实际上被解释为一个正确的方法调用(到目前为止在链中构造的派生最多的对象)?

除了这些小问题,它提出了两个重要问题:

1。在派生的构造函数中调用基础构造函数总是会在首先调用派生构造函数之前调用默认的基础构造函数吗?这不是效率低下的吗?

2。是否有一个用例,其中默认的基础构造函数,每#1,不应该代替在派生类中显式调用的基础构造函数&#39;构造函数?如何在C ++中实现?

我知道#2听起来很愚蠢,毕竟你并不能保证派生类的基类部分的状态已经准备好了#39; /&#39;构建&#39;如果你可以推迟调用基础构造函数,直到派生构造函数中的任意函数调用。例如:

derived::derived() { base::base(); }

...我期望以相同的方式行事并且两次调用基础构造函数。但是有一个原因,编译器似乎将它视为与此相同的情况?

derived::derived() : base() { }

我不确定。但就观察到的影响而言,这些似乎是等同的陈述。它违背了我的想法,基本构造函数可以转发(至少在某种意义上)或者更好的选择是选择使用:base()语法的派生类。实际上,该表示法要求在成员与派生类不同之前放置基类......

换句话说this answer and it's example(暂时忽略它的C#)会调用基础构造函数两次?虽然我理解为什么会这样做,但我不明白它为什么表现得不那么直观&#34;并选择基础构造函数(至少对于简单的情况)并且只调用一次。

是否存在双重初始化对象的风险?或者是在编写构造函数代码时假设对象未初始化的部分原因?在最糟糕的情况下,我现在必须假设每个类成员都可能被初始化两次并防范它吗?

我将以一个可怕的例子结束 - 但这不会泄漏记忆吗?是否应该泄漏?

#include <iostream>
using namespace std;
struct base2 {
  int * member;
  base2() : member(new int) {}
  base2(int*m) : member(m) {}
  ~base2() {if (member) delete member;}
};
struct derived2 : base2 {
  derived2() : base2(new int) {
    // is `member` leaking?
    // should it be with this syntax?
  }
};
int main() {
  derived2 d;
  return 0;
}

2 个答案:

答案 0 :(得分:2)

  

但这不会泄漏内存吗?是否应该泄漏?

没有。操作顺序为:

derived2::derived2()
  auto p = new int
  base2::base2(p)
   base2::member = p

对于析构函数:

derived2::~derived2() (implied)
 base2::~base2()
  if (base2::member) { delete base2::member; }

一个新的,一个删除。完美。

不要忘记编写正确的赋值/复制构造函数。

答案 1 :(得分:0)

构造派生类对象编译器需要构造其基础部分。您可以指定derived2() : base2(new int)应使用哪个基类构造函数编译器。

如果缺少此类规范,编译器将使用基本默认构造函数。

因此,基本构造函数只会被调用一次,只要你的代码没有导致内存泄漏,就不会有任何一个。