0

我看到了两种使用成员初始化器列表的不同方法。第一个是这样的:

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变量,ChildClass然后它会看到这样的变量没有在ChildClass.

我不清楚的第二点是为什么我们可能想在第二个示例中的构造函数的主体中放置一些内容。即使没有任何东西,它已经使用父类的构造函数创建并返回了一个对象。

4

4 回答 4

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);
  }
};

有人可能想要这样做的原因可能有很多。一般来说,由于子类比父类“更多”,如果它想在其构造函数中做更多而不是仅仅初始化基类子对象,那是很自然的。

关于对象生命周期的一些话:在进入构造函数体之前,你只构造了新对象的子对象。当执行离开构造函数体时,对象本身开始其生命周期。一个类比可以是汽车及其零件:在进入汽车的构造器车身之前,您已经给轮胎充气,组装发动机并冲压出车身部件。但是,如果您的汽车尚未组装,则它不是汽车,这发生在构造函数体内。这反映在析构函数中,尤其是在存在异常的情况下可以看到:如果在对象的构造函数期间发生异常,则不会调用其析构函数。只有已经完全构造的子对象的析构函数才会被调用。这是因为如果构造函数还没有完成执行,

于 2013-08-15T08:38:37.473 回答
1

ParentClass是一个类型并且varName1是一个变量。它们是每个编译器都应该区分的两种不同类型的实体。

在很多情况下,您希望将一些代码放入子类 ctor 中。例如,您想根据基类对象的正确初始化来计算子类成员的初始值。

于 2013-08-15T08:34:24.353 回答
0

这些对于编译器来说实际上是相同的:初始化列表用于初始化子对象。如果子对象是基类,则按其类型命名;如果是会员,则以会员名命名;但是这两种情况的原理是一样的。

通常的名称查找规则适用。如果你给一个成员与基类同名,它将隐藏基类,并且你不能在初始化列表中指定基类(这意味着基类必须有一个默认的构造函数)。(不要这样做。建立命名约定,使类型名称和变量名称永远不会冲突。)

至于为什么您可能需要在构造函数的实际主体中编写代码,可能有很多原因。大多数情况下,这将是因为一些后处理你要对初始化的成员做什么。在其他情况下,可能是因为您需要在初始化成员之前进行一些预处理。或者您可能希望将一些常见的处理重构为一个单独的函数。

于 2013-08-15T08:49:13.473 回答
-1

我不清楚的是编译器如何区分这两种情况(语法相同)。例如,在第二个例子中,编译器可能认为它需要取 name 变量的值并将其分配给 ChildClass 的 ParentClass 变量,然后它会看到这样的变量没有在 ChildClass 中声明。

编译器知道这ParentClass是一个类型而不是ChildClass. 您将无法声明具有名称的成员ParentClass

我不清楚的第二点是为什么我们可能想在第二个示例中的构造函数的主体中放置一些内容。即使没有任何东西,它已经使用父类的构造函数创建并返回了一个对象。

这适用于您想要在ParentClass.

于 2013-08-15T08:24:15.403 回答