构造函数成员初始值列表(member initialization list)

为什么对象使用前需要初始化

1.读取未初始化的值会导致不明确的行为
  在某些平台上,仅仅是读取未初始化的值,就可能让你的程序终止运行。更可能的情况是读入一些“半随机”bits,污染了正在进行读取动作的那个对象,最终导致不可预知的程序行为,以及许多令人不愉快的调试过程。
  永远在使用对象之前将它初始化:对于无任何成员的内置类型,你必须手工完成此事;对于内置类型以外的任何其他东西,初始化责任落在构造函数身上,规则是:确保每一个构造函数都将对象的每一个成员初始化。
2.构造函数的初始值有时候必不可少
(1) 当初始化一个reference menber时;
(2) 当初始化一个const menber时;
(3) 当调用一个base class的constructor,而它拥有一组参数时;
(4) 当调用一个member class的constructor,而它拥有一组参数时。

初始化和赋值的区别

1.底层效率
  初始化操作直接初始化成员,赋值操作则先初始化再赋值。例如:
(1) 没有使用成员初始化列表

class Word {
    string _name;
    int _cnt;
public:
    //没有使用member initialization list
    Word() {
        _name = 0;
        _cnt = 0;
    }
};

  在这里,Word constructor会先产生一个临时性额String object,然后将它初始化,之后以一个saaignment运算符将临时性object指定给_name,随后再摧毁那个临时性object。以下是constructor可能的内部扩张结果:

//C++伪代码
Word::Word() {
    //调用String的default constructor
    _name.String::String();

    //产生临时对象
    String temp = String(0);

    //将temp每个成员都拷贝给_name
    _name.String::operator=(temp);

    //析构临时对象
    temp.String::~String();


    _cnt = 0;
}

(2) 使用初始化列表

//使用初始化列表
Word::Word() : _name(0) {
    _cnt = 0;
}

  它会被扩张成这样:

//C++伪代码
Word::Word() {
    //调用String(int) constructor
    _name.String::String(0);
    _cnt = 0;
}

  这个构造函数和上一个的最终结果相同,但通常效率高很多。对于大多数类型而言,比起先调用default constructor然后再调用copy assignment操作符,值调用一次copy constructor是比较高效的。

2.一些成员必须要求初始化
  如果成员是const或者是引用,必须将其初始化;当成员属于某种类类型而且该类设有定义默认构造函数时,也必须将这个成员初始化。

成员初始化顺序

1.C++成员初始化次序
  C++有着十分固定的成员初始化次序:base classes更早于其derived classes被初始化,而class的成员变量总是以其声明次序被初始化。构造函数初始值列表中初始值的前后位置关系不会影响实际的成员初始化顺序。例如:

Word::Word() : _cnt(0), _name(0) {
}

  编译器会一次操作initialization list,以适当顺序在constructor之内安插初始化操作,并且在任何explicit user code之前。例如,上面的Word constructor被扩充为:

//C++伪代码
Word::Word() {
    _name.String::String(0);
    _cnt(0);
}

  列表中的初始化顺序是由class中的members声明顺序决定的,不是成员初始值列表的排列顺序决定的。

2.一个成员用另一个成员来初始化
  举个例子:

class X {
    int i;
    int j;
public:
    X(int val) : j(val), i(j) {}
};

  i先被初始化,因为这个初始值的效果是试图使用未定义的值j初始化i。
  测试代码如下:

#include<iostream>
using namespace std;

class X{
public:
    int i;
    int j;
public:
    //有陷阱的写法
    X(int val) : j(val), i(j) {}
}; 

class Y {
public:
    int i;
    int j;
public:
    //修改后的写法
    Y(int val) : j(val) {
        i=j;
    }
};

int main() {
    X x(3);
    Y y(5);

    cout<<"x.i="<<x.i<<" x.j="<<x.j<<endl;
    cout<<"y.i="<<y.i<<" y.j="<<y.j<<endl;

    return 0;
}

这里写图片描述

  x.i的值果然不是我们所期望的3。

总结

  • 为内置类型对象进行手动初始化,因为C++不能保证初始化它们;
  • 构造函数最好使用成员初始值列表(member initialization list),而不要在构造函数本体内使用赋值操作(assignment);
  • 最好令构造函数初始值的顺序与成员变量的顺序保持一致,如果可能的话,尽量避免使用某些成员初始化其他成员。

参考资料

  • 《C++ Primer》7.5 构造函数再探
  • 《Effective C++》 条款4 确定对象被使用前已先被初始化
  • 《深度探索C++对象模型》 2.4 成员们的初始化队伍
  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
可以在类的构造函数中使用静态数据成员,但不能在构造函数中初始化静态数据成员。 静态数据成员是类的所有对象共享的,它们在类的整个生命周期中保持不变。因此,通常应该在类定义的外部进行静态数据成员的初始化。如果在构造函数中初始化静态数据成员,可能会导致不必要的重复初始化或者出现未定义的行为。 以下是一个错误示例,试图在类的构造函数中初始化静态数据成员: ```c++ class MyClass { public: MyClass() { static_data_member = 10; // 错误,不能在构造函数中初始化静态数据成员 } void print_static_data_member() { cout << static_data_member << endl; } private: static int static_data_member; }; int main() { MyClass obj; obj.print_static_data_member(); // 输出 0 return 0; } // 编译错误:error: ISO C++ forbids in-class initialization of non-const static member ‘MyClass::static_data_member’ ``` 在上面的示例中,编译器将会报错,因为不能在构造函数中初始化静态数据成员。 如果要在类中使用静态数据成员,可以直接使用,例如: ```c++ class MyClass { public: MyClass() {} void print_static_data_member() { cout << static_data_member << endl; } private: static int static_data_member; }; int main() { MyClass::static_data_member = 10; MyClass obj; obj.print_static_data_member(); // 输出 10 return 0; } ``` 在上面的示例中,我们直接使用了静态数据成员`static_data_member`,并在`main()`函数中将其初始化为`10`,然后在`MyClass`的成员函数中使用它。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值