C++中有两种类型的成员初始值设定项列表吗

Are there two types of member initializer lists in C++?

本文关键字:列表 两种 类型 成员 C++      更新时间:2023-10-16

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

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变量的值,并将其分配给ChildClassParentClass变量,然后它发现这样的变量没有在ChildClass中声明。

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

我不清楚的是编译器如何区分这两种情况(语法相同)。

看到列表中的初始值设定项,编译器首先查找具有该名称的成员变量。如果它找到一个,它会尝试用给定的参数初始化该成员。如果没有,它将查找具有该名称的直接基类或虚拟基类,以使用给定的参数初始化基类子对象。当您在类的方法(包括构造函数)中命名某个对象时,通常会应用这些名称查找规则。

在极少数且设计糟糕的情况下,当成员变量和直接基类都具有相同名称时,必须限定基类类型,即添加名称空间:

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

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

关于对象生存期的一些词语:在进入构造函数主体之前,您只构造了新对象的子对象。当执行离开构造函数主体时,对象本身开始其生存期。一个类比可以是汽车及其零件:在进入汽车的构造车身之前,你已经给轮胎充气,组装好发动机,并冲压出车身的零件。但是,如果你的车还没有组装好,它就不是一辆车,这种情况发生在车身上。这在析构函数中是镜像的,尤其是在存在异常的情况下:如果在对象的构造函数中发生异常,则不会调用其析构函数。只有已经完全构造的子对象的析构函数才会被调用。这是因为,如果构造函数尚未完成执行,则对象永远不存在,也没有什么可调用的析构函数。

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

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

这些对编译器来说都是一样的:初始值设定项列表用于初始化子对象。如果子对象是基础类,它是根据其类型命名的;如果是成员,则命名为按成员名称;但这两种情况的原理是相同的。

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

至于为什么您可能希望在构造函数,可能有很多原因。大多数情况下由于一些后期处理,您在初始化的成员。在其他情况下,可能是因为需要先进行一些预处理,然后才能初始化成员。或者您可能想要重构一些常见的处理输出到一个单独的函数中。

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

编译器知道ParentClass是一个类型,而不是ChildClass中的成员。您将无法声明名为ParentClass 的成员

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

这适用于想要在ParentClass中使用某些特定的非默认构造函数的情况。