Something you need to know about INITIALIZATION LISTS in Constructor of C++

本文深入探讨了C++中使用初始化列表初始化构造函数成员的目的、常见场景及注意事项,包括效率提升、引用类型和const数据成员的初始化、继承关系中的构造函数调用等,并提供了一个实际例子来说明如何正确使用初始化列表。

(今天和一个同事闲聊时,扯到这个问题,随手把自己的想法记录下来,后续慢慢补充)


首先,为什么要使用 INITIALIZATION LISTS?
主要是基于效率的原因:正常的构造函数在函数内部进行数据成员的赋值,但是数据成员又一次隐含的默认构造函数调用是你看不到的。
而使用初始化列表初始化数据成员则只调用一次赋值操作符赋值。


我们从一个例子开始,看看我们最常见的在构造函数中使用初始化列表的场景:
class Array
{
public:
    explicit Array(int size) :
        mSize(size),
        mData(new int[size]){}
    
    Array(const Array &in_array) :
        mSize(in_array.mSize),
        mArray(new std::string[in_array.mSize])
    {
        std::copy(in_array.mArray, in_array.mArray+mSize, mArray);
    }

    Array &Array::operator=(const Array &in_array)
    {
        if (this != &in_array) // check for self assignment
        {
            delete [] mArray;  // delete current array first
            mSize = in_array.mSize;
            mArray = new std::string[in_array.mSize];
            std::copy(in_array.mArray, in_array.mArray+mSize, mArray);
        }
        return *this;
    }

    ~Array()
    {
        delete [] mData;
    };

    int Get(int index) const;
    void Set(int index, int value);
    int GetSize() const;
private:
    int mSize;
    int* mData;
};

Tip 0. 不能在初始化列表中被初始化的场景:
数组不能在初始化列表中初始化,这种情况完全可以使用std::vector。
构造函数的初始化列表中不能给static数据进行初始化,必须在构造函数体内进行赋值。

Tip 1. 必须使用初始化列表的场景:

含有引用类型数据成员,必须在初始化列表中初始化;

const数据成员,必须在初始化列表中初始化;

继承关系中,父类没有默认构造函数,但是含有父类数据成员,子类初始化时必须显示的调用父类的构造函数,在初始化列表中初始化。

Tip 2. 继承关系中,子类在初始化列表中调用父类构造函数,初始化父类数据。
  • 对于构造函数,初始化子类数据成员之前,最好显示的调用父类的构造函数初始化父类数据成员,如果父类提供了默认构造函数,那么子类构造函数初始化列表中没有显示调用,会隐式的调用。
  • 子类初始化列表中,调用父类构造函数和子类数据成员赋值之间的声明的先后顺序没有强制要求,不管怎样,肯定是先调用父类的构造函数之后再初始化子类数据。不过最好在初始化列表中先调用父类构造函数
  • 对于子类的拷贝构造函数,初始化列表初始化时,需要显示的调用父类的构造函数初始化父类的数据成员。
Tip 3. 
从个人的经验角度,在设计时需要特别考虑到,抽象出的基类最好只是一个接口类,不含有数据只描述公共的行为,这样子类只是继承了父类的行为,而不涉及具体父类数据的继承。
如果需要设计抽象的数据类,可以采用类组合的方式,而不是类继承。这样可能更加灵活和方便。

Example 1 :

#include <iostream>
#include <string>
using namespace std;
class Base
{
private:
    int _id;
public:
    Base()
    { 
        _id = 1;
        cout<<"default constructor is called\n"; 
    }
    Base(int id):_id(id)
    { 
        cout<<"constructor is called\n";
    }
    ~Base()
    {
        cout<<"Base class destructor is called."<<endl;
    }
    int GetId()
    {
        return _id;
    }
};
class Derive:public Base
{
private:
    string _name;
public:
    Derive(string name, int id):Base(id), _name(name)
    {
        cout<<"Derive class constructor is called."<<endl;
        cout<<"Id is: "<<GetId()<<endl;
        cout<<"Name is: "<<GetName()<<endl;
    }
    Derive(string name):_name(name)
    {
        cout<<"Derive class constructor is called."<<endl;
        cout<<"Id is: "<<GetId()<<endl;
        cout<<"Name is: "<<GetName()<<endl;
    }
    ~Derive()
    {
        cout<<"Derive class destructor is callled."<<endl;
    }
    string GetName()
    {
        return _name;
    }
};
int main()
{
    Derive* a = new Derive("fuqiang", 10);
    
    Derive* b = new Derive("Walle");
    getchar();
    return 0;
}


Tip 4. 需要防止构造函数初始化时的资源泄露。详细参见:More effective C++, Item M10。

Tip 5. 如果你在设计的软件是API,不希望把实现细节暴露在头文件中,那么你可以在头文件中正常声明,而在cpp文件中定义的时候采用初始化列表初始化数据成员

(暂时想到这几点,最近比较懒,最近抽空把说明代码补上。)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值