我觉得这是不可能的,例如:
Calling the constructor of the base class after some other instructions in C++
但是下面的程序运行并生成两行“构造函数人”:
#include <iostream>
class Person
{
public:
Person()
{
std::cout << "Constructor Person" << std::endl; }
};
class Child : public Person
{
public:
Child()
{
c = 1;
Person();
}
int c;
};
int main()
{
Child child;
return 0;
}
第一个是默认构造函数的隐式调用,这是明确的。第二个怎么样 - 这是否意味着标题中描述的行为是合法的?我使用Visual C ++ 2010。
答案 0 :(得分:12)
子类构造函数内部的调用不是调用基类构造函数,而是创建一个类型为Person的临时,未命名和新对象。它将在构造函数退出时被销毁。为了澄清,您的示例与执行此操作相同:
Child() { c = 1; Person tempPerson; }
除非在这种情况下,临时对象具有名称。
如果你稍微修改一下你的例子,你可以看到我的意思:
class Person
{
public:
Person(int id):id(id) { std::cout << "Constructor Person " << id << std::endl; }
~Person(){ std::cout << "Destroying Person " << id << std::endl; }
int id;
};
class Child : public Person
{
public:
Child():Person(1) { c = 1; Person(2); }
int c;
};
int main() {
Child child;
Person(3);
return 0;
}
这会产生输出:
Constructor Person 1
Constructor Person 2
Destroying Person 2
Constructor Person 3
Destroying Person 3
Destroying Person 1
答案 1 :(得分:6)
您无法从子构造函数的主体调用它,但您可以将其放入初始化列表中:
public:
Child() : Person() { c = 1; }
当然,调用父项的默认构造函数是没有用的,因为这会自动发生。如果需要将参数传递给构造函数,它会更有用。
您无法从正文中调用构造函数的原因是因为C ++保证父代将在子构造函数启动之前完成构造。
答案 2 :(得分:6)
以下是“Accelerated C ++”的摘录:
“派生的物体由以下构成:
1.为整个对象分配空间(基类成员以及派生类成员);
2.调用基类构造函数初始化对象的基类部分;
3.按照构造函数初始值设定项的指示初始化派生类的成员;
4.执行派生类构造函数的主体,如果有的话。“
总结答案和注释:在上面的#2必须在#4之前的意义上,从子类的构造函数体调用基类的构造函数是不可能的。 但是我们仍然可以在派生的构造函数体中创建一个基础对象,从而调用基础构造函数。它将是一个与使用当前执行的派生构造函数构造的对象不同的对象。
答案 3 :(得分:0)
这个问题的答案虽然通常在技术上是正确的和有用的,但请不要大张旗鼓。而且总体情况与看起来有些不同:)
总是会调用基类的构造函数 ,否则在派生类的构造函数的主体中,您将拥有部分构造的对象,因此无法使用。您可以通过 option 为基类构造函数提供参数。这不会“调用”它:无论如何,它都会被调用,您只需将一些额外的参数传递给它即可:
// Correct but useless the BaseClass constructor is invoked anyway
DerivedClass::DerivedClass() : BaseClass() { ... }
// A way of giving arguments to the BaseClass constructor
DerivedClass::DerivedClass() : BaseClass(42) { ... }
用于显式调用构造函数的C ++语法具有一个怪异的名称,并且不辜负这个名称,因为这很少执行-通常仅在库/基础代码中执行。它称为 placement new ,不,它与内存分配无关-这是在C ++中显式调用构造函数的怪异语法:
// This code will compile but has undefined behavior
// Do NOT do this
// This is not a valid C++ program even though the compiler accepts it!
DerivedClass::DerivedClass() { new (this) BaseClass(); /* WRONG */ }
DerivedClass::DerivedClass() { new (this) BaseClass(42); /* WRONG */ }
// The above is how constructor calls are actually written in C++.
所以,在您的问题中,这是您想问的问题,但不知道:)我想这种奇怪的语法很有用,因为如果简单,那么来自这种构造函数调用的语言的人就很普遍了。 (例如Pascal / Delphi)可以编写许多看似有效的代码,这些代码可能会以各种方式完全破坏。 未定义的行为并不能保证会崩溃,这就是问题所在。表面/明显的UB通常会导致崩溃(例如空指针访问),但是很多UB都是无声的杀手。因此,通过使某些语法晦涩难懂来编写错误代码变得更加困难,这是一种语言的理想特性。
问题中的“第二个选项”与构造函数“ calls”无关。创建值为BaseClass
对象的默认构造实例的C ++语法为:
// Constructs a temporary instance of the object, and promptly
// destructs it. It's useless.
BaseClass();
// Here's how the compiler can interpret the above code. You can write either
// one and it has identical effects. Notice how the scope of the value ends
// and you have no access to it.
{
BaseClass __temporary{};
}
在C ++中,构造对象实例的概念无所不在:您一直都在这样做,因为语言语义将对象的存在等同于已构造的对象。所以你也可以这样写:
// Constructs a temporary integer, and promptly destructs it.
int();
整数类型的对象也可以构造和销毁-但是构造函数和析构函数很简单,因此没有开销。
请注意,以这种方式构造和破坏对象并不意味着任何堆分配。如果编译器决定必须实际实现一个实例(例如,由于构造或破坏的可观察到的副作用),则该实例是一个临时对象,就像在表达式求值期间创建的临时对象一样-a-ha,我们注意到{{ 1}}是一个表达式!
因此,在您的情况下,type()
语句是无操作的。在以发布模式编译的代码中,不会为它生成任何机器指令,因为无法观察到该语句的效果(对于特定的Person();
类而言),因此,如果没有人可以听到树倒下,那么树就不必首先存在。这就是C ++编译器如何优化内容的方法:他们做了很多工作来证明(从数学意义上来说,形式上是正式的)任何代码的效果是否都是不可观察的,如果是,则将这些代码视为无效代码并删除。