C++ 控制派生类中的基构造函数,可能是双重初始化

C++ 控制派生类中的基构造函数,可能是双重初始化,c++,c++11,constructor,virtual-functions,C++,C++11,Constructor,Virtual Functions,关于这个问题,似乎有一个例外,但它给我提出的问题比回答问题更多。考虑这一点: #include <iostream> using namespace std; struct base { virtual void test() {cout << "base::test" << endl;} base() {test();} virtual ~base() {} }; struct derived : base { virtual void te

关于这个问题,似乎有一个例外,但它给我提出的问题比回答问题更多。考虑这一点:

#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;
}
。。。我希望以同样的方式调用基本构造函数两次。然而,编译器似乎将其视为与此相同的情况,这是有原因的吗

derived::derived() : base() { }
我不确定。但就观察到的效应而言,这些似乎是等价的陈述。它与我所想到的基本构造函数可以被转发(至少在某种意义上)的想法背道而驰,或者使用
:base()
语法在派生类中选择更好的词。事实上,这种表示法要求基类放在派生类不同的成员之前

换句话说(暂时忘记它的C#)会调用基构造函数两次吗?虽然我理解它为什么会这样做,但我不理解它为什么不会表现得更“直观”,并选择基本构造函数(至少对于简单的情况)并只调用一次

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

我将以一个可怕的例子结束——但这不会泄漏内存吗?它应该会泄漏吗

#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;
}
#包括
使用名称空间std;
结构base2{
国际*成员;
base2():成员(新int){}
base2(int*m):成员(m){}
~base2(){if(member)delete member;}
};
结构派生2:base2{
derived2():base2(新int){
//“成员”是否泄漏?
//应该使用这种语法吗?
}
};
int main(){
衍生的2d;
返回0;
}

要构造派生类对象,编译器需要构造其基本部分。您可以通过
derived2():base2(newint)
指定基类构造函数编译器应该使用的

若您缺少这样的规范,编译器将使用基本默认构造函数


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

要构造派生类对象,编译器需要构造它的基部分。您可以通过
derived2():base2(newint)
指定基类构造函数编译器应该使用的

若您缺少这样的规范,编译器将使用基本默认构造函数

所以,基构造函数将只被调用一次,并且只要您的代码没有导致内存泄漏,就不会有任何泄漏

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

#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;
}
否。操作顺序为:

derived2::derived2()
  auto p = new int
  base2::base2(p)
   base2::member = p
对于析构函数:

derived2::~derived2() (implied)
 base2::~base2()
  if (base2::member) { delete base2::member; }
一个新的,一个删除。太好了

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

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

#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;
}
否。操作顺序为:

derived2::derived2()
  auto p = new int
  base2::base2(p)
   base2::member = p
对于析构函数:

derived2::~derived2() (implied)
 base2::~base2()
  if (base2::member) { delete base2::member; }
一个新的,一个删除。太好了


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

第一个示例是使用“隐式int”返回类型的无效代码。函数
test
需要指定的返回类型。请修复并重新检查。编辑的程序不显示您描述的行为。请参见使用
void
返回类型编译代码,但我无法重现隐含的编译器错误。无论如何,对于编译器错误,编译器和版本以及完整调用(逐字)是相关的。注意,谢谢。我知道这并没有表现出问题,我需要向它添加更多的工作代码(我不认为涉及到它)现在必须结束,我的示例中充斥着宏。TL;DR
base
也继承自一个类,该类定义了用于跟踪目的的每个构造函数和运算符(非平凡构造函数)。基本上,我需要重新解释整个问题,示例不适合,对不起…第一个示例是无效代码,使用“implicit int”返回类型。函数
test
需要指定的返回类型。请修复并重新检查。编辑的程序不显示您描述的行为。请参见使用
void
返回类型编译代码,但我无法重现隐含的编译器错误。无论如何,对于编译器错误,编译器和版本以及完整调用(逐字)是相关的。注意,谢谢。我知道这并没有表现出问题,我需要向它添加更多的工作代码(我不认为涉及到它)现在必须结束,我的示例中充斥着宏。TL;DR
base
也继承自一个类,该类定义了用于跟踪目的的每个构造函数和运算符(非平凡构造函数)。基本上,我需要重新解释整个问题,这个例子并不适合,对不起…谢谢,这是我认为应该发生的事情,但我设法打破了这一点。当我弄明白那是什么时,如果我有什么想法的话,我会再问一次。关于
derived::derived(){base::base();}
。。。MSV确实调用了
base()
两次。这看起来像是shortgun…Clang的工作原理与MSV相同,gcc无法编译,并显示消息“无法直接调用构造函数'D2::B2[-fppermissive]”。谢谢,这是我认为应该发生的事情,但我设法打破了这一点。当我弄明白那是什么时,如果我有什么想法的话,我会再问一次。关于
derived::derived(){base::base();}
。。。MSV确实调用了
base()
两次。这看起来像是shortgun…Clang的工作原理与MSV相同,gcc无法编译,并显示消息“无法直接调用构造函数'D2::B2'[-fppermissive]”