C++构造函数体中赋值和列表初始化的差异

  • Post author:
  • Post category:其他


初始化和赋值的差异

对于类的构造函数,有如下两种形式:

第一种是不使用初始化列表的:

class B
{
public:
    B(const A & item){
        _a = item;
    };
    ~B(){};

private:
    A _a;
};

第二种是使用初始化列表的,形如:

class B
{
public:
    B(const A & item):_a(item){
    };
    ~B(){};

private:
    A _a;
};

我们这里之所以使用一个自定义的类A作为类B的成员,是想通过自定义的复制控制函数来看看究竟以上二者间到底有什么区别。完整的代码如下:

#include <iostream>

using std::cout;
using std::endl;

class A
{
public:
    A() {
        cout << "A()" << endl;
    }
    A(const A & other) {
        cout << "A(const A & other)" << endl;
    }
    A & operator=(const A & other) {
        cout << "operator=(const A & other)" << endl;
        return *this;
    }
    ~A() {
        cout << "~A()" << endl;
    }
};

class B
{
public:
    B(const A & item):_a(item){
//      _a = item;
    };
    ~B(){};

private:
    A _a;
};

int main()
{
    A a;
    B b(a);
    system("pause");
    return 0;
}

我们测试后发现输出如下:

B 输出
非参数列表 A() A() operator=(const A & other)
参数列表 A() A(const A & other)

由此可见,在第一种形式下,B类对象的构造过程实际上是先调用了A类的默认构造函数,然后再调用了赋值函数完成了B类的A对象成员的构造;而在第二种形式下,直接是调用了复制构造函数一步完成的。

必须使用初始化列表的场景

根据上面的分析,我们就不难理解为什么说一些特定场景下必须要使用初始化列表了,

总结起来

就是:


如果类的成员是const、引用、或者是未提供默认构造函数的类,我们就必须通过构造函数初始化列表为这些成员提供初值


详细说明如下

(1)需要初始化的数据成员是对象的情况(包含继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化);

数据成员是对象,并且这个对象只有含参数的构造函数,没有无参数的构造函数。如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,而没有默认的构造函数,这时要对其进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,就无法完成。

(2)需要初始化const修饰的类成员或初始化引用数据成员;

当类成员中含有一个const对象时,或者一个引用时,必须经过成员初始化列表进行初始化,因为const对象或者引用在声明的同时必须初始化,而在构造函数中,做的是对它们的赋值,并不是初始化。

(3)子类初始化父类的私有成员

子类初始化父类的私有成员,需要在(并且只能在)参数初始化列表中显示调用父类的构造函数。


根据《c++ primer》 给出的建议:

由于在很多类中,初始化和赋值的区别事关底层效率的问题:我们举个例子看看链表吧,做一次赋值操作很有可能要先做delete之后再做一次复制构造的全过程,代价是巨大的,因此应该养成使用初始化列表的习惯。

相对于在构造函数体中用显式的赋值语句来设置成员变量的值,还有另一种更高效的技术,即使用成员初始化列表。

下面使用类 Box 的构造函数的另一个版本来说明:

Box::Box(double lv, double wv, double hv):length {lv}, width {wv}, height {hv}
{
    std::cout << "Box constructor called." << std::endl;
}

成员变量的值在构造函数头的初始化列表中被指定为初始值。例如,成员变量 length 用 lv 初始化。构造函数的初始化列表与参数列表用冒号分隔开,每个初始值用逗号分隔开。

在使用构造函数体中的赋值语句初始化成员变量时,首先要创建成员变量(如果这是类的一个实例,就调用构造函数),再执行赋值语句。如果使用初始化列表,成员变量在被创建时,就用初始值对它进行初始化。效率要比在构造函数体中使用赋值语句高得多,特别是在成员变量是一个类实例时,就更是如此。

有一个地方需要注意:成员变量的初始化顺序由类定义中声明成员变量的顺序决定。因此,可能与读者预期的不同,并不是按照它们在成员初始化列表中出现的顺序进行初始化。当然,只有当使用表达式来初始化成员变量时,或者当通过调用一个成员函数来初始化变量,但是该成员函数依赖于另一个已经初始化的成员变量时,就会出现这种情况。在生产中依赖这种计算顺序可能很危险。即使今天所有代码都正常工作,但是明年,其他人可能改变声明顺序,导致类的构造函数不再正确。

一般来说,首选在构造函数的成员初始化列表中初始化所有成员变量。这样做一般更加高效,为了避免产生混淆,最好按照类定义中成员变量的声明顺序,在初始化列表中列举成员变量。只有当需要更加复杂的逻辑时,或者当初始化成员变量的顺序很重要时,才应该在构造函数内初始化成员变量。




注:不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行。



static数据成员不会影响该类及其对象的sizeof.


Center

uploading.4e448015.gif


正在上传…


重新上传


取消






版权声明:本文为wads23456原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。