为什么我更喜欢使用成员初始化列表?
我偏爱使用成员初始化列表与我的构造函数…但我早已忘记了背后的原因…
你在构造函数中使用成员初始化列表吗? 如果是这样,为什么? 如果没有,为什么不呢?
对于POD类的成员来说,没有什么区别,只是风格问题。 对于类的成员,它避免了对默认构造函数的不必要的调用。 考虑:
class A { public: A() { x = 0; } A(int x_) { x = x_; } int x; }; class B { public: B() { ax = 3; } private: A a; };
在这种情况下, B
的构造函数将调用A
的默认构造函数,然后将ax
初始化为3.更好的方法是让B
的构造函数直接调用构造函数中的A
的构造函数:
B() : a(3) { }
这只会调用A
的A(int)
构造函数,而不是它的默认构造函数。 在这个例子中,差异是可以忽略的,但是想象一下,如果你将A
的默认构造函数做得更多,例如分配内存或打开文件。 你不会想这样做不必要的。
而且,如果一个类没有默认的构造函数,或者你有一个const
成员variables,那么你必须使用初始化列表:
class A { public: A(int x_) { x = x_; } int x; } class B { public: B() : a(3), y(2) // 'a' and 'y' MUST be initialized in an initializer list; { // it is an error not to do so } private: A a; const int y; };
除了上面提到的性能原因外,如果你的类存储了作为构造函数parameter passing的对象的引用,或者你的类有constvariables,那么除了使用初始值设定项列表之外,你没有任何select。
- 基类的初始化
使用构造函数初始化列表的一个重要原因是这里没有提到的答案是基类的初始化。
按照build设的顺序,应该在孩子上课前build造基础class。 没有构造函数初始化列表,如果你的基类有默认的构造函数,在进入子类的构造函数之前调用它,这是可能的。
但是,如果你的基类只有参数化构造函数,那么你必须使用构造函数初始化列表来确保你的基类在子类之前被初始化。
-
初始化只有参数化构造函数的子对象
-
效率
使用构造函数初始值设定项列表,可以将数据成员初始化为代码中所需的确切状态,而不是先将它们初始化为默认状态,然后将其状态更改为代码中所需的状态。
- 初始化非静态常量数据成员
如果类中的非静态常量数据成员具有默认构造函数,并且您不使用构造函数初始化函数列表,那么您将无法将它们初始化为预期状态,因为它们将被初始化为默认状态。
- 引用数据成员的初始化
当编译器input构造函数时,引用数据成员必须初始化,因为引用不能在稍后声明和初始化。 这只有在构造函数初始化列表中才有可能。
除了性能问题之外,还有另一个非常重要的问题,我称之为代码可维护性和可扩展性。
如果T是POD,并且您开始更喜欢初始化列表,那么如果一次T将变成非PODtypes,则不需要在初始化周围更改任何内容,以避免不必要的构造函数调用,因为它已经被优化。
如果typesT确实有默认的构造函数和一个或多个用户定义的构造函数,并且您决定删除或隐藏默认构造函数,那么如果使用初始化列表,则不需要更新代码,因为用户定义的构造函数是因为他们已经正确实施。
与const成员或引用成员一样,假设最初T定义如下:
struct T { T() { a = 5; } private: int a; };
接下来,您决定将const定义为const,如果从头开始使用初始化列表,那么这是一个单行更改,但是如上所述定义了T,还需要挖掘构造函数定义以删除赋值:
struct T { T() : a(5) {} // 2. that requires changes here too private: const int a; // 1. one line change };
如果代码不是由“代码猴”编写的,而是由工程师根据他正在做的事情做出更深入的考虑而做出决定,那么维护更容易,更不容易出错。
在构造函数的主体运行之前,调用其父类的所有构造函数,然后调用它的字段。 默认情况下,调用无参构造函数。 初始化列表允许您select调用哪个构造函数以及构造函数接收哪些参数。
如果您有一个引用或一个常量字段,或者如果其中一个类没有默认构造函数,则必须使用初始化列表。
// Without Initializer List class MyClass { Type variable; public: MyClass(Type a) { // Assume that Type is an already // declared class and it has appropriate // constructors and operators variable = a; } };
这里编译器遵循以下步骤来创build一个MyClasstypes的对象
1.types的构造函数首先被称为“a”。
2.“types”的赋值运算符在MyClass()构造函数体内调用
variable = a;
-
最后,“Type”的析构函数因为超出范围而被称为“a”。
现在考虑与具有初始化列表的MyClass()构造函数相同的代码
// With Initializer List class MyClass { Type variable; public: MyClass(Type a):variable(a) { // Assume that Type is an already // declared class and it has appropriate // constructors and operators } };
使用初始化程序列表,编译器会遵循以下步骤:
- 调用“Type”类的拷贝构造函数来初始化variables(a)。 初始化列表中的参数用于直接复制结构“variables”。
- “types”的析构函数被称为“a”,因为它超出了范围。
句法:
class Sample { public: int Sam_x; int Sam_y; Sample(): Sam_x(1), Sam_y(2) /* Classname: Initialization List */ { // Constructor body } };
需要初始化列表:
class Sample { public: int Sam_x; int Sam_y; Sample() */* Object and variables are created - ie:declaration of variables */* { // Constructor body starts Sam_x = 1; */* Defining a value to the variable */* Sam_y = 2; } // Constructor body ends };
在上面的程序中,当类的构造函数被执行时, Sam_x和Sam_y被创build。 然后在构造函数体中定义那些成员数据variables。
用例:
- 一个类中的Const和引用variables
在C中, 必须在创build过程中定义variables。 在C ++中使用相同的方法,我们必须使用初始化列表在对象创build期间初始化Const和Referencevariables。 如果我们在创build对象之后(内部构造函数体)进行初始化,我们会得到编译时错误。
-
Sample1(base)类的成员对象没有默认的构造函数
class Sample1 { int i; public: Sample1 (int temp) { i = temp; } }; // Class Sample2 contains object of Sample1 class Sample2 { Sample1 a; public: Sample2 (int x): a(x) /* Initializer list must be used */ { } };
为派生类创build对象,派生类将在内部调用派生类构造函数并调用基类构造函数(默认值)。 如果基类没有默认构造函数,则用户将得到编译时错误。 为了避免,我们必须要么
1. Default constructor of Sample1 class 2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
-
Class构造函数的参数名和Data类的成员是一样的:
class Sample3 { int i; /* Member variable name : i */ public: Sample3 (int i) /* Local variable name : i */ { i = i; print(i); /* Local variable: Prints the correct value which we passed in constructor */ } int getI() const { print(i); /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/ return i; } };
众所周知,如果两个variables都具有相同的名称,则具有最高优先级的局部variables然后是全局variables。 在这种情况下,程序考虑“我”价值{左右variables。 即:i = i}作为Sample3()构造函数中的局部variables,并且Class成员variables(i)被覆盖。 为了避免,我们必须使用
1. Initialization list 2. this operator.