如果这些函数出错的化,将会遍及整个class
条款05:了解C++默默编写并调用哪些函数
真讨厌,编译器会帮你添加上一些函数
如果你写下
class Empty{};
就好像如下
class Empty
{
public:
Empty() {...}; //default构造函数
Empty(const Empty& rhs) {...} //copy构造函数
~Empty() {...} //析构函数
//virtual..
Empty& operator = (const Empty& rhs){...} //copy assign操作符
}
如果你声明了其中一个(default构造函数,拷贝构造函数,copy assign构造函数)构造函数,则编译器不会为你创建default构造函数;但如果你没声明拷贝构造函数,copy assign构造函数,但调用他们的时候,编译器会为你创建他们。
上面的意思就是说
只要你自己写了三个构造函数当中的任何一个,编译器都不会帮你产生default构造函数了,如果需要使用/调用,你需要自己去写一个;但其他的(拷贝构造函数,copy assign构造函数)只要你没写,编译器就会帮你生成一个(当然是在你调用的时候)
条款6 若不想使用编译器自动生成的函数,就该明确拒绝
一些member成员函数和friend函数还是可以调用private函数,“将成员函数声明为private函数,而不去实现它”是常用的伎俩。
这里举一个例子 class HomeForSale{…}, 这个类不想被赋值(=)或拷贝(HomeForSale a(b)),
因此可以这样
class HomeForSale
{
public:
...
private:
HomeForSale(const HomeForSale&);
HomeForSale operator = (const HomeForSale&);
};
//但是这个要实现构造函数
可是一些member成员函数和friend函数还是可以调用private函数的,所以说,编译阶段不会出错,链接阶段会出错,可以使用下面方法将错误提前到编译阶段
class Uncopyable
{
protected:
Uncopyable();
~Uncopyable();
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator = (const Uncopyable&);
};
class HomeForSale : private Uncopyable //class不再声明copy构造函数或copy assign操作符
{
...
}
因为HomeForSale内有memeber函数或友元函数想要尝试拷贝HomeForSale对象,编译器便会尝试生成一个copy构造函数或copy assign操作符,则会去调用其base类的copy构造函数或copy assign操作符,而base类的是private,因此编译会失败。
另外boost有个可使用版本boost::noncopyable。???查下使用方法
条款7:为多态基类声明为virtual析构函数
当drived class对象由一个base class指针被删除,而该base class带有一个non-virtual析构函数,其结果未定义,实际执行的时候通常发生的是对象的drived成分没被销毁,因此解决办法给base class 一个virtual析构函数,此后删除drived class对象就会如你想要的那般
但是当class不企图被当作base class,令其西沟函数为virtual往往是个馊主意,为什么呢?因为C++析构函数的实现是由vptr(virtual table pointer)指向一个vtbl(virtual table),每一个带有virtual函数的class都有一个相应的vtbl,这样class的内存空间会加大!!
即使class完全不带virtual函数,被”non-virtual析构函数问题”给咬伤还是有可能的,如std::string就不含任何virtual函数,如下面错误
class SpecialString : public std::string{...}
这样在delete的时候就可能出错,包括所有的STL容器如vector,list,set,tr1::unordered_map等等,所以说不要企图继承一个标准容器,或是带有non-virtual析构函数的class
- 也可以创建一个带有pure virtual析构函数,如下
class AWOV
{
public:
virtual ~AWOV() = 0; //声明pure virtual析构函数
};
但是必须为pure virtual函数提供一份定义
AWOV()::~AWOV(){}
因为析构函数运作方式:先调用drived的析构函数,再调用base class的析构函数,如果没有定义的话,链接器会发生抱怨的。
条款8 别让异常逃离析构函数
。。。
条款9 绝不在构造和析构过程中调用virtual函数
base class构造期间virtual函数绝不会下降到drived class阶层,或另一种说法base class构造期间,virtual函数就不是virtual函数,对于析构函数也如此。至于原因理由看书吧,
以后需要特别注意class的创建和销毁的时机,是否已经创建完毕,是否已经正确销毁,很重要。那如何转换呢?看一个例子
代码更改前
class Transaction
{
public:
Transaction();
virtual void log() const = 0;
};
Transaction::Transaction()
{
...
log();
}
class BuyTransaction : public Transaction
{
public:
virtual void log() const;
}
class Transaction
{
public:
explicit Transaction(const string& loginfo);
void log(const string& loginfo) const;
};
Transaction::Transaction(const string& info)
{
...
log(info);
}
class BuyTransaction : public Transaction
{
public:
BuyTransaction(para)
: Transaction(createLog(para))
{...}
private:
static string createLog(para);
}
条款10 令operator=返回一个reference to *this
一般这样做
class Widget
{
public:
Widget& operator = (const Widget& rhs)
{
...
return *this;
}
};
这个不仅适用于以上的标准赋值形式,也适用于所有赋值相关运算,比如operator +=,等。
这个不遵循还是可以编译通过的,但是string, vector,complex, tr1:shared_ptr都共同准守
条款11 在operator=中处理“自我赋值”
一般而言,如果某段代码操作了pointers或references,而它们被用来,就需要处理自我赋值,如果你会运用对象来管理资源,大可不必额外操心,但如果你尝试自行管理资源,可能会掉进“在停止使用资源前意外释放它”的陷阱。如下
版本1
class Bitmap {...};
class Widget
{
...
private:
Bitmap *pb;
};
Widget& operator = (const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
这里自我赋值出现的问题,this已被删除,却返回一个指针指向一个已被删除的对象。版本1不具备“自我赋值安全性”和“异常安全性”
传统做法如下,版本2
Widget& operator = (const Widget& rhs)
{
if (this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
版本2存在异常方面的麻烦,如果new Bitmap导致异常(分配内存不足或Bitmap的copy构造函数抛出异常),Widget最终会持有一个指针指向一块被删除的Bitmap,这样的指针时有害的,无法安全删除,也无法读取,只能debug去找。哎
但,让operator=具“备异常安全”往往自动获得“自我赋值安全”的回报,因此愈来愈多人对“自我赋值”的处理态度是不去管它,把焦点放在“异常安全性”
版本3
Widget& operator = (const Widget& rhs)
{
Bitmap *pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
如果”new Bitmap”抛出异常,pb保持原状;此代码也能够处理自我赋值
版本4 copy and swap技术,一个足够好而常见的版本但是清晰性不好
class Widget
{
...
void swap(Widget& rhs);
}
Widget& operator = (const Widget& rhs)
{
Widget temp(rhs);
swap(temp);
return *this;
}
Widget& Widget::operator = (Widget rhs)
{
swap(rhs);
return *this;
}
条款12 赋值对象时勿忘其每一个成分
如果你为class添加一个成员变量,你必须同时修改copying函数(拷贝构造函数和copy assign构造函数)。
条款12的意思是,当你编写一个copying函数时,请确保(1)复制所有local成员变量,(2)调用所有base classes内合适的copying函数
而且注意drived class的拷贝构造函数调用base的拷贝构造函数,不要试图调用base的cooy assign函数来初始化,对于drived的copy assign函数也一样,如果你觉得代码可能会有重复的地方,那么可以新建一个private的init()函数。