Effective C++笔记2

来源:互联网 发布:淘宝大v达人申请 编辑:程序博客网 时间:2024/05/17 23:50

【条款11】为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
默认的拷贝构造函数和赋值运算符,是浅拷贝的。如果有指针成员变量,默认只会拷贝指针值,而不会为指针分配新的内存。这会带来两个问题:
1.内存泄露。拷贝指针时,并没有释放原来指向的内存,导致这部分内存泄露;
2.两个指针指向同一块内存。如果一个对象的生命周期结束,释放了这部分内存,另一个指针就会指向已经释放的内存。这个对象生命周期也结束时,会delete已经delete了的指针。
例:
string a("hello");
{
 string b("world");
 b = a;   // "world"所占内存泄露了
}
string c = a;   // b生命周期结束,释放了"hello"内存,a和c的值不确定

 

【条款12】尽量使用初始化,而不要在构造函数里赋值
在使用成员之前,要确保它已经被赋值,有两种方法:
1.初始化。方法是用初始化列表;
2.在构造函数中赋值。
如果使用初始化,会调用成员变量的构造函数;如果在构造函数中赋值,会先调用成员变量的默认构造函数进行初始化,然后调用operator=运算符进行赋值。
很明显,使用初始化效率比较高。
另外,如果成员变量是const或引用,则不得不用初始化列表,否则会编译错误。

 

【条款13】初始化列表中成员列出的顺序和它们在类中声明的顺序相同
成员按照声明的顺序初始化,与初始化列表中的顺序无关。否则,类需要记住初始化列表顺序,然后按照反顺序调用析构函数,太麻烦了。
如果初始化列表中的顺序和声明的顺序不一致,可能会带来问题,例如用一个未初始化的变量初始化另一个变量。
基类初始化应该放在初始化列表的最前面。如果有多个基类,初始化顺序应和继承顺序一致。

 

【条款14】确定基类有虚析构函数
B继承了A。A类型指针p指向B对象。delete p时,如果A的析构函数不是虚函数,就没有多态,只调用了A的析构,而没有调用B的析构,这可能会引起内存泄漏。

 

【条款15】让operator=返回*this的引用
string& operator=(const string& rhs); // 将一个string赋给一个string


问题1:为什么参数要是const的?
 x = "hello";
 编译器会产生一个临时string变量,并且是const的。如果参数不是const的,x = "hello"这种用法就不对了。


问题2:为什么要返回string&,而不是void?
 int w, x, y, z;
 w = x = y = z = 0;
 固定类型可以这样赋值,我们自定义的类应该跟其保持一致。如果返回void,就不能连续赋值了。


问题3:为什么不能返回参数rhs?
 int i1, i2, i3;
 ...
 (i1 = i2) = i3;      
 对于固定类型,这样是合法的,i2赋给i1,然后i3赋给i1。
 如果返回参数rhs,它是const的,就不能这样用了。
综上,operator=应当返回*this的引用。
string& string::operator=(const string& rhs)
{
  ...
  return *this;     
}

 

【条款16】在operator=中对所有数据成员赋值
在operator=中,应该对所有数据成员赋值。
比较容易出错的是继承的时候,忘了调用基类的operator=。这样基类就不会被赋值。
基类operator=的调用方法:
derived& derived::operator=(const derived& rhs)
{
  if (this == &rhs) return *this;

  base::operator=(rhs);    // 调用this->base::operator=
  y = rhs.y;

  return *this;
}
或者:
derived& derived::operator=(const derived& rhs)
{
  if (this == &rhs) return *this;

  static_cast<base&>(*this) = rhs;      // 对*this的base部分
                                        // 调用operator=
  y = rhs.y;

  return *this;
}
注意,*this转成base&,而不是base,否则会调用copy构造函数生成新的base对象,并对这个新对象赋值。

拷贝构造函数类似于operator=,一定要记得调用基类的拷贝构造函数。
class derived: public base {
public:
  derived(const derived& rhs): base(rhs), y(rhs.y) {}
  ...
};

 

【条款17】在operator=中检查给自己赋值的情况
检查给自己赋值的情况,基于两个原因:
1.效率。如果是给自己赋值,没必要真的去赋值,这样可以节省赋值动作及可能的函数调用(base部分operator=)。
2.保证正确性。如果有指针成员,在赋值前要先释放内存,然后分配新的内存。给自己赋值的情况下,释放内存后,没有拷贝需要的源数据了,这是灾难性的。

检查方法:
c& c::operator=(const c& rhs)
{
  // 检查对自己赋值的情况
  if (*this == rhs)            // 假设operator=存在
    return *this;
  ...
}

给自己赋值,看起来可能性不大,a=a,显得无聊吗。
但是不要忘了别名的存在。a=b,b可能是a的一个别名。
所以,任何函数,只要有别名存在,都要考虑两个变量是同一个变量的情况。

 

【条款18】争取使类的接口完整并且最小

原创粉丝点击