窥视C++细节-为什么成员初始化列表更快

环境

在运行测试代码时,使用了如下环境:

linux使用的是ubuntu 18,在ubuntu上使用的是g++,版本如下:

root@learner:~# g++ --version
g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

必须使用成员初始化列表的情况

成员变量是引用类型
class A
{
public:
    A(int i)
    :ri(i){}

private:
    int & ri;
};

int main()
{
    int i = 100;
    A a(i);

    return 0;
}

如果数据成员变量是引用类型,则必须在初始化列表中进行初始化。

成员变量是const类型
class A
{
public:
    A(int i)
    :ci(i){}

private:
    const int ci;
};

int main()
{
    A a(100);

    return 0;
}
有参基类的构造器

基类的有参构造器必须在初始化列表中调用。

class A
{
public:
    A(int i)
    :i_(i){}

private:
    int i_;
};

class B : public A
{
public:
    B(int x)
    :A(x){}     //调用父类的有参构造器

};

int main()
{
    B b(100);

    return 0;
}
有参类类型数据成员构造器

类数据成员是类对象,且需要调用其有参构造器,那么只能在初始化列表中调用。

class A
{
public:
    A(int i)
    :i_(i){}

private:
    int i_;
};

class B
{
public:
    B(int x)
    :a(x){}  //调用类数据成员的有参构造器
    
private:
    A a;
};

int main()
{
    B b(100);

    return 0;
}

应注意的问题

参数列表初始化顺序,跟变量的声明顺序有关,跟初始化列表中的顺序无关。

class Stu
{
public:
    Stu(string na)
        :name(na),len(strlen(name.c_str())){}

    void print()
    {
        cout<<"len = "<<len<<", name = "<<name<<"."<<endl;
    }

private:
    int len;      
    string name;  
};

int main()
{
    Stu s("Jasmine");
    s.print();
    
    return 0;
}
len = 30, name = Jasmine.

出错了,name的长度不是30。

初始化列表的顺序是,先初始化name,再初始化len。实际的初始化顺序是,先初始化len,但在初始化是name还没有初始化,所以求出的长度是不对的;再初始化name。

class Stu
{
public:
    Stu(string na)
        :len(strlen(na.c_str())),name(na){}   //正确的方式

    void print()
    {
        cout<<"len = "<<len<<", name = "<<name<<"."<<endl;
    }

private:
    int len;      //先初始化 len
    string name;  //再初始化 name
};

int main()
{
    Stu s("Jasmine");
    s.print();
    
    return 0;
}
len = 7, name = Jasmine.

正确的方式

初始化列表快的原因

定义一个用于测试的类
class A
{
public:
    A(){
        cout<<"default constructor: "<<this<<endl;
        data_ = 0;
    }

    A(int i){
        cout<<"construtor: "<<this<<endl;
    }

    A(const A& a)
    {
        cout<<this<<" : copy constructor form : "<<&a<<endl;
        data_ = a.data_;
    }
    A& operator=(const A & a){
        cout<<this<<" operator= "<<&a<<endl;
        data_ = a.data_;
    }

    ~A()
    {
        cout<<"deconstructor : "<<this<<endl;
    }

private:
    int data_;
};
不使用初始列表
class B 
{
public:
    B(int data){
        a = data;
    }

private:
    A a;
};

int main()
{
    B b(100);

    return 0;
}
default constructor: 0x7ffecef57d94
construtor: 0x7ffecef57d64
0x7ffecef57d94 operator= 0x7ffecef57d64
deconstructor : 0x7ffecef57d64
deconstructor : 0x7ffecef57d94

在这里插入图片描述

data赋值给a对象时,先是发生调用构造进行一次隐式转换,将隐式转换的结果赋值给对象a,发生赋值运算符重载,临时对象消失时,会调用析构器。

使用初始化列表
class B 
{
public:
    B(int data)
        :a(data){}

private:
    A a;
};

int main()
{
    B b(100);

    return 0;
}
construtor: 0x7ffe0fba5494
deconstructor : 0x7ffe0fba5494

现在仅需要一次构造即可

总结

从上面的两个测试例子中,对于类类型的数据成员,使用初始化列表仅需一次有参构造;不使用初始化列表,则会发生一次默认构造、一次有参构造、一次赋值运算符重载、一次析构。所以使用初始化列表比不使用快很多。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值