我看到了使用成员初始化列表的两种不同方法。第一个是这样的:
class ClassName {
public:
arg_type_1 varName1;
arg_type_2 varName2;
// Constructor.
ClassName(arg_type_1 arg_name_1, arg_type_2 arg_name_2)
: varName1(arg_name_1), varName2(arg_name_2)
{
}
}
有什么事情发生了。在构造函数中,我们有一个参数列表,我们使用它们来初始化类的成员。例如,arg_name_1
用于初始化类的varName1
变量的值。
使用成员初始化程序的另一种方法出现在继承的情况下:
class ChildClass : public ParentClass
{
ChildClass(string name) : ParentClass( name )
{
// What can we put here and why we might need it.
}
};
这里发生的事情也很清楚。当我们使用一个字符串参数调用ChildClass
的构造函数时,它使用相同的字符串参数调用ParentClass
的构造函数。
我不清楚的是编译器如何区分这两种情况(语法是相同的)。例如,在第二个示例中,编译器可能认为它需要获取name
变量的值并将其分配给ParentClass
的{{1}}变量,然后它会看到这样的变量变量未在ChildClass
中声明。
第二个不清楚的地方是我为什么要在第二个例子中将一些内容放在构造函数体中。即使没有任何东西它已经创建并使用父类的构造函数返回一个对象。
答案 0 :(得分:3)
我不清楚的是编译器如何区分这两种情况(语法是相同的)。
在列表中查看初始化程序时,编译器首先查找具有该名称的成员变量。如果找到一个,它会尝试使用给定的参数初始化该成员。如果没有,它会查找具有该名称的直接基类或虚基类,以使用给定的参数初始化基类子对象。当您在类的方法(包括构造函数)中命名时,这些是通常的名称查找规则。
在罕见且设计糟糕的情况下,如果同时拥有成员变量和具有相同名称的直接基类,则必须限定基类类型,即添加名称空间:
struct B1 {
B1(char const*) {};
};
namespace Foo {
struct B2 {
B2(bool) {};
};
}
struct Weird : public B1, public Foo::B2 {
int B1;
double B2;
Weird()
: ::B1("meow")
, Foo::B2(false)
, B1(42)
, B2(3.14)
{}
};
第二个不清楚的地方是我为什么要在第二个例子中将一些内容放在构造函数体中。即使没有任何东西它已经创建并使用父类的构造函数返回一个对象。
它并不真正返回任何对象,它只是创建该对象。但是,有人可能想要调用这样的附加功能:
class ParentClass {
public:
ParentClass(string name);
void registerSomething(ParentClass const&);
}
class ChildClass : public ParentClass {
public:
ChildClass(string name) : ParentClass( name ) {
registerSomething(*this);
}
};
可能有很多原因导致有人想要这样做。一般来说,由于子类比父类“更多”,所以如果它想在构造函数中做更多而不仅仅是初始化基类子对象,那是很自然的。
关于对象生命周期的一些话: 在进入构造函数体之前,您只构造了新对象的子对象。当执行离开构造函数体时,对象本身就开始了它的生命周期。一个类比可以是汽车及其零件:在对汽车的构造体进行操作之前,您已经给轮胎充气,组装了发动机并冲压出车身部件。但如果没有组装,那么你的车不是车,而是在构造体中发生的。这在析构函数中被镜像,尤其是在存在异常的情况下可以看到:如果在对象的构造函数期间发生异常,则不会调用其析构函数。只调用已经完全构造的子对象的析构函数。这是因为如果构造函数没有完成执行,则该对象永远不存在,并且没有任何东西可以调用析构函数。
答案 1 :(得分:1)
ParentClass
是一种类型,varName1
是一个变量。它们是每个编译器应该区分的两种不同的实体。
当你想在子类ctor中放入一些代码时,有很多情况。例如,您希望根据基类对象的正确初始化来计算子类成员的初始值。
答案 2 :(得分:0)
这些对于编译器来说实际上是相同的:初始化列表 用于初始化子对象。如果子对象是基础 类,它的类型命名;如果它是一个成员,它被命名 按会员名称;但两种情况下的原则都是一样的。
通常的名称查找规则适用。如果你给一个成员了 与基类同名,它会隐藏基类,而你 无法在初始化列表中指定基类(哪个 表示基类必须具有默认构造函数)。 (不要这样做。建立命名约定以便输入 名称和变量名称永远不会发生冲突。)
至于为什么你可能想要代码在实际的体内 构造函数,可能有很多原因。大多数情况下,它会 因为有些后期处理你该做什么 初始化成员。在其他情况下,可能是因为你 在初始化之前需要进行一些预处理 成员。或者您可能想要重构一些常见的处理 进入一个单独的职能。
答案 3 :(得分:-1)
我不清楚的是编译器如何区分这两种情况(语法是相同的)。例如,在第二个示例中,编译器可能认为它需要获取name变量的值并将其分配给ChildClass的ParentClass变量,然后它会看到这样的变量未在ChildClass中声明。
编译器知道ParentClass
类型不是ChildClass
中的成员。您将无法声明名为ParentClass
第二个不清楚的地方是我为什么要在第二个例子中将一些内容放在构造函数体中。即使没有任何东西它已经创建并使用父类的构造函数返回一个对象。
这适用于您想在ParentClass
中使用某些特定非默认构造函数的情况。