准备实现gof上面一个迭代器模式,用到了上面的List基本类,但是一直对赋值函数和拷贝构造函数不是很熟悉,就研读了一下effective c++的关于这方面的一章,颇有收获,抽取了我认为精华的部分分享给大家。由于一直对c++这一类的用法不是很熟悉,有错误或者优化或者需要特别强调的地方希望朋友们帮忙指出来,我再仔细分析改正。
故而例如一个string类。
string a;
string b = a;//由于b还没有被创建,故而调用的是拷贝构造函数。针对这种情况一般写为string b(a);
string c;
b = c;//由于b已经被创建,调用的是赋值函数。
被赋值的对象存不存在是判断调用的拷贝构造还是赋值函数的根本。
解决这类指针混乱问题的方案在于,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。在这些函数里,你可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者你可以采用某种引用计数机制(见条款 m29)去跟踪当前有多少个对象指向某个数据结构。引用计数的方法更复杂,而且它要求构造函数和析构函数内部做更多的工作,但在某些(虽然不是所有)程序里,它会大量节省内存并切实提高速度。
如果你真的很确信不需要拷贝构造和赋值的时候,可以只声明这些函数(声明为private成员)而不去定义(实现)它们。这就防止了会有人去调用它们,也防止了编译器去生成它们。这样在你试图进行赋值或者copy的时候,编译器会给你相应的错误信息,避免你由于无意而进行这些操作
尽量使用成员初始化列表而不用构造函数赋值。对象的创建分两步,首先是数据成员初始化,然后调用构造函数体。这样如果类的对象成员的初始化在构造函数中的话,就会被初始化两次,一次调用默认构造函数,一次调用赋值函数。如果在成员初始化列表中,就会仅仅调用一次拷贝构造函数来初始化。特别是const和引用数据成员只能用初始化,不能被赋值。“插一句关于const引用的总结:const引用只是表明不能通过此引用间接的改变被引用的对象,对象能不能被更改,取决于对象本身,仅仅是通过引用不可以更改,const放前放后没有关系。"
摘录的effective c++的原话:
养成尽可能使用成员初始化列表的习惯,不但可以满足const和引用成员初始化的要求,还可以大大减少低效地初始化数据成员的机会。但有一种情况下,对类的数据成员用赋值比用初始化更合理。这就是当有大量的固定类型的数据成员要在每个构造函数里以相同的方式初始化的时候。
manydatambrs::manydatambrs()
: a(1), b(1), c(1), d(1), e(1), f(1), g(1), h(1), i(0),
j(0), k(0), l(0), m(0)
{ ... }
manydatambrs::manydatambrs(const manydatambrs& x)
: a(1), b(1), c(1), d(1), e(1), f(1), g(1), h(1), i(0),
j(0), k(0), l(0), m(0)
{ ... }
void manydatambrs::init()
{
a = b = c = d = e = f = g = h = 1;
i = j = k = l = m = 0;
}
函数定义的写法:
string& string::operator=(const string& rhs)
{
if (this == &rhs) return *this;
...//如果是派生类,还需要调用基类的赋值函数base::operator=(rhs);
//释放自身动态分配的内存,将rhs的成员赋值给自己
return *this; // 返回左边的对象
}
string::string(const string& rhs)
{
*this = rhs;//调用赋值函数
}
当定义自己的赋值运算符时,必须返回赋值运算符左边参数的引用,*this。如果不这样做,就会导致不能连续赋值,或导致调用时的隐式类型转换不能进行,或两种情况同时发生。
类里有指针的时候,就要写自己版本的拷贝构造和赋值函数。
如果确认不会有拷贝和赋值的情况,那就声明这些函数为private类型,阻止编译器生成默认的。
尽量采用成员初始化列表,少使用构造函数赋值。针对const和引用数据成员,必须采用初始化列表。
赋值函数,需要判定是否自己对自己赋值,如果是派生类需要调用基类的赋值函数,如果自己已经alloc了内存,需要首先释放,然后赋值,最好返回*this.
拷贝构造函数可以通过调用赋值函数来实现构造操作。