Effective C++ : initialization list vs assignments

tips:4 确定对象在使用前已被初始化

构造函数的初始化列表比在构造函数内部赋值操作更加高效,
初始化列表只调用了类的初始化函数,也就是成员变量的构造函数一次
构造函数内部的赋值操作,首先需进行初始化,然后再进行构造函数的赋值。这样就相当于赋值两次。唯一的麻烦就是必须要记住初始化列表中初始化参数的顺序一定要和类中成员的声明的次序一致。初始化的顺序是从左向右的。
下面用程序说明
首先写测试类A
//test initial list and construct which is faster
class A
{
private:
	string a;
	string b;
	int c;
public:
	A(const string & a,const string & b );
	A(const string & a,const string & b ,int c);
};
A::A(const string & thisa,const string & thisb )
{
	a=thisa;
	b=thisb;
	c=0;
}
A::A(const string & thisa,const string & thisb, int thisc):a(thisa),b(thisb),c(thisc)
{

}

然后在主函数里写:
	A *test = new A("a","b");
	A *cont = new A("a","b",3);

第一个构造函数是赋值构造函数
第二个是初始化列别构造函数
我们分别查看他们两个的执行过程
首先查看第一个的 new 函数在堆上分配对象内存 其中size大小为68=32+32+4是为对象的成员变量的内存空间
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                static const std::bad_alloc nomem;
                _RAISE(nomem);
                }

        return (p);
        }

2:重点,调用string b的初始化函数,也就是copy构造函数在xstring里面
	basic_string(const _Elem *_Ptr)
		: _Mybase()
		{	// construct from [_Ptr, <null>)
		_Tidy();
		assign(_Ptr);
		}
其中,*_Ptr 的值是'b'
3: 重点,调用string a的初始化函数,也就是copy构造函数在xstring里面
	basic_string(const _Elem *_Ptr)
		: _Mybase()
		{	// construct from [_Ptr, <null>)
		_Tidy();
		assign(_Ptr);
		}

其中,*_Ptr 的值是'a'
4:以上只是对字符串的实参的赋值操作,现在才开始执行构造函数
A::A(const string & thisa,const string & thisb )
{
	a=thisa;
	b=thisb;
	c=0;
}
先进行初始化
step into 查看一下
	basic_string()
		: _Mybase()
		{	// construct empty string
		_Tidy();
		}

然后继续执行又调用了一次
	basic_string()
		: _Mybase()
		{	// construct empty string
		_Tidy();
		}
以上是对两个string类型的实参进行初始化的步骤
5:下面是赋值操作的过程
对于第一个语句
	a=thisa;

	_Myt& operator=(const _Myt& _Right)
		{	// assign _Right
		return (assign(_Right));
		}

_Right的值是"a"

对于第二个语句
	b=thisb;

	_Myt& operator=(const _Myt& _Right)
		{	// assign _Right
		return (assign(_Right));
		}

其中
_Right 
 的值是"b"
对于C=0 
内置类型直接赋值,没有调用系统函数
6:下面就开始对各种资源的析构
首先对第一个构造函数中的资源进行释放

	~basic_string()
		{	// destroy the string
		_Tidy(true);
		}

又调用了一次这个函数
	~basic_string()
		{	// destroy the string
		_Tidy(true);
		}
7:下面初始化成员列表的构造函数正式出场了
首先是new,size=68=32+32+4
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                static const std::bad_alloc nomem;
                _RAISE(nomem);
                }

        return (p);
        }

8:实参对形参的赋值 
	basic_string(const _Elem *_Ptr)
		: _Mybase()
		{	// construct from [_Ptr, <null>)
		_Tidy();
		assign(_Ptr);
		}
*_Ptr的值是'b'
然后是a的赋值
	basic_string(const _Elem *_Ptr)
		: _Mybase()
		{	// construct from [_Ptr, <null>)
		_Tidy();
		assign(_Ptr);
		}

*_Ptr的值是'a'

10:调用对形参初始化的COPY构造函数。
	basic_string(const _Myt& _Right)
		: _Mybase(_Right._Alval)
		{	// construct by copying _Right
		_Tidy();
		assign(_Right, 0, npos);
		}

_Right 是"a"
Xstring 是这样定义_Myt的
typedef basic_string<_Elem, _Traits, _Ax> _Myt;
也就是现在是字符串
然后再次重复以上过程对另一个string类型的形参b进行初始化
	basic_string(const _Myt& _Right)
		: _Mybase(_Right._Alval)
		{	// construct by copying _Right
		_Tidy();
		assign(_Right, 0, npos);
		}

_Right 的值是“b”
此时构造结束
11:返回主函数,进行析构
	~basic_string()
		{	// destroy the string
		_Tidy(true);
		}

再次调用
	~basic_string()
		{	// destroy the string
		_Tidy(true);
		}
其中释放资源的函数
	void _Tidy(bool _Built = false,
		size_type _Newsize = 0)
		{	// initialize buffer, deallocating any storage
		if (!_Built)
			;
		else if (this->_BUF_SIZE <= this->_Myres)
			{	// copy any leftovers to small buffer and deallocate
			_Elem *_Ptr = this->_Bx._Ptr;
			if (0 < _Newsize)
				_Traits::copy(this->_Bx._Buf, _Ptr, _Newsize);
			this->_Alval.deallocate(_Ptr, this->_Myres + 1);
			}
		this->_Myres = this->_BUF_SIZE - 1;
		_Eos(_Newsize);
		}


	void _Eos(size_type _Newsize)
		{	// set new length and null terminator
		_Traits::assign(_Myptr()[this->_Mysize = _Newsize], _Elem());
		}

总结:
程序总步骤
1、初始化实参 此时是‘a’ , 'b' 自负,然后去初始化字符串,以后变为“a” , "b"
2、初始化两个string
3、进行函数体内操作(构造函数1:进行赋值,构造函数2:无操作)
4、析构函数释放资源


小结:
初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数都可以。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。但有的时候必须用带有初始化列表的构造函数:
1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
2.
const成员引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。 

初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
首先把数据成员按类型分类并分情况说明:
1.内置数据类型,复合类型(指针,引用)
    在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
2.用户定义类型(类类型)
    
结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值