细读《Effective C++》之二

来源:互联网 发布:房天下源码 编辑:程序博客网 时间:2024/04/28 16:21

第二章 Constructors, Destructors, and Assignment Operators

这一章内容参考Stanley B. Lippman的《Inside the C++ Object Model》也会有不小的收获。

条款05:Know what functions C++ silently writes and calls

在最早接触C++的class的时候,你就被告知每一个class在缺省情况下都会具有:default constructors、copy constructors、destructors和copy assignment operators。

当对象的成员也是复杂对象的时候,在使用copy constructor和copy assignment operator时,要注意成员对象的copy constructor和copy assignment operator应该是逻辑正确的(而不仅是语法正确的)。在使用copy constructor和copy assignment operator时,要注意成员对象的copy constructor和copy assignment operator应该是逻辑正确的(而不仅是语法正确的)。

当对象的成员是复杂对象的引用的时候,当复杂对象含有const成员的时候,问题变得更加复杂:当多个引用指向同一个对象并试图对其进行修改时,危险是显而易见的,而更改const成员更是不合法的。

template<class T>
class NamedObject {
public:
  
// this ctor no longer takes a const name, because nameValue
  
// is now a reference-to-non-const string. The char* constructor
  
// is gone, because we must have a string to refer to.
  NamedObject(std::string& name, const T& value);
  ...                               
// as above, assume no operator= is declared

private:
  std::
string& nameValue;           // this is now a reference
  const T objectValue;              // this is now const
}
;

还有就是当base class声明其copy assignment operator为private时,编译器不会为derived class自动生成copy assignment operator,对于derived class的对象无法调用的私有成员函数,只能在编译时报错了。

using namespace std;

template 
<typename T>
class CBase
{
public:
......

private:
 CBase 
&operator=(const CBase &rhs){m_T = rhs.m_T;}  // 不能被子类的drv2访问

private:
 T m_T;
}
;

template 
<typename T>
class CDerived : public CBase <T>
{
......
}
;

int _tmain(int argc, _TCHAR* argv[])
{
 CDerived
<int> drv1, drv2;
 
 drv2 
= drv1;

 
return 0;
}

条款06:Explicitly disallow the use of compiler-generated functions you do not want

能够让编译器自动为我们生成default constructors、copy constructors、destructors和copy assignment operators,这一切看上去很安逸,可是如果你不需要它们呢?或者你不希望别人使用它们呢?在STL中很多对象是不允许使用copy constructors和copy assignment operators。如果你不想别人用,那就给它个private吧!这样,即使是其它member functions or friend function霸王硬上弓也将导致linking error,想让这样的error也在compiling时期报告出来,让你的类从Uncopyable派生:

class Uncopyable {
protected:                                   // allow construction
  Uncopyable() {}                            // and destruction of
  ~Uncopyable() {}                           // derived objects...

private:
  Uncopyable(
const Uncopyable&);             // ...but prevent copying
  Uncopyable& operator=(const Uncopyable&);
}
;

条款07:Declare destructors virtual in polymorphic base classes

在最早接触destructor的时候,我所见到的destructor前面大多是有virtual的,很长一段时间都以后那是习惯,抑或是规定。直到有人提出why,我才跟着想:是啊,Why?

如果derived class object比base class object数据成员更多(大多数情况下是这样),这意味着derived class object占用了更多的内存。一个指向derived class object的base class type pointer被delete时,被销毁的自然只会是base class type大小的内存块。

为什么不加个virtual呢?问题解决了。

Stanley(Stanley B. Lippman,《Inside the C++ Object Model》)和Scott都告诉我们,如果一个class含virtual function(包括virtual destructor),该class会包含一个vtbl(virtual table),而其对象则必然有一指向vtbl的vptr(virtual pointer)。

这样来看,把一个不带virtual destructor的类作为base class是不合适的,而让一个不可能充当base class的类成员函数带上virtual也是没有道理的。即使一个类可能作为base class,而不用于polymorphic用途也无需声明virtual destructor。

个人认为,只有当derived class object和base class object所占内存大小不一致时,virtual destructor才是必需的,其它情况下都没有必要。

条款08:Prevent exceptions from leaving destructors

异常处理也是一个常说常新的话题,Scott假定exceptions出现在destructors当中,很难想像destructors中的excelptions handles再次抛出异常会怎么样。

Scott说:Destructors should never emit exceptions,否则:they're in no position to complain if the class swallows the exception or terminates the program. After all, they had first crack at dealing with the problem, and they chose not to use it.

看来,异常处理还是应该放在普通函数中处理才好。

条款09:Never call virtual functions during construction or destruction

这一条款初看莫名其妙:constructor/destructor和virtual functions的关系有什么特别吗?仔细一想:construct derived class object时,先调用的是base class的constructor,则derived class的成员显然不可能被初始化,所调用的virtual functions也不可能被指为derived class。析构时刚好相反,derived class object的成员变量被析构掉后,base class中也无法看到它们。

条款10:Have assignment operators return a reference to *this

并非什么强制规定,仅仅是为了能够连续赋值:

int x, y, z;

// x = (y = (z = 15));
= y = z = 15;                        // chain of assignments

class Widget {
public:
  ...
Widget
& operator=(const Widget& rhs)   // return type is a reference to
{                                      // the current class
  ...
  
return *this;                        // return the left-hand object
}

  ...
}
;

条款11:Handle assignment to self in operator=

= w;                                            // assignment to self
a[i] = a[j];                                      // potential assignment to self
*px = *py;                                        // potential assignment to self

 

class Bitmap { ... };

class Widget {
  ...
private:
  Bitmap 
*pb;                                     // ptr to a heap-allocated object
}
;

Widget 
&Widget::operator=(const Widget& rhs)      // unsafe impl. of operator=
{
// 1 unsafe
  delete pb;                                      // stop using current bitmap
  
// 2 safe : unless new operation failed
  if (this != &rhs)                               // identity test: if a self-assignment, do nothing
  {
    pb 
= new Bitmap(*rhs.pb);                     // start using a copy of rhs's bitmap
  }


// 3 safe : no identity test
  Bitmap *pOrig = pb;                             // remember original pb
  pb = new Bitmap(*rhs.pb);                       // make pb point to a copy of *pb
  delete pOrig;                                   // delete the original pb

// 4 safe : moving the copying operation from the body of the function
//        to construction of the parameter
  Widget temp(rhs);                               // make a copy of rhs's data
  swap(temp);                                     // swap *this's data with the copy's

  
return *this;                                   // see Item 10
}

个人喜欢第2种和第4种。

条款12:Copy all parts of an object

void logCall(const std::string& funcName);        // make a log entry
class Customer {
public:
  ...
  
// 自己声明copying functions
  Customer(const Customer& rhs);
  Customer
& operator=(const Customer& rhs);
  ...

private:
  std::
string name;
  Date lastTransaction;
}
;

此时的问题是,如果你不能保证每一个成员变量都被copy,编译器不会告诉你这一点,即使是继承时也不会。

When you're writing a copying function, be sure to:

(1) Copying functions should be sure to copy all of an object's data members and all of its base class parts.

(2) Don't try to implement one of the copying functions in terms of the other. Instead, put common functionality in a third function that both call.

原创粉丝点击