现在的位置: 首页 > 综合 > 正文

c++笔记 构造/析构/赋值运算

2013年08月27日 ⁄ 综合 ⁄ 共 2826字 ⁄ 字号 评论关闭

1.copy构造函数与copy assignment操作符
copy 构造函数被用来“以同型对象初始化自我对象”,copy assignment操作符被用来“从另一个同型对象中拷贝其值到自我对象”。

class Widget {
	Widget();								//default构造函数
	Widget(const Widget& rhs);				//copy构造函数
	Widget& operator=(const Widget& ths);	//copy assignment操作符
}
Widget w1;  //调用default构造函数
Widget W2;  //调用copy构造函数
w1 = w2;	//调用copy assigment操作符

当你看到赋值符号时请小心,因为"="语法也可用来调用copy构造函数:
Widget w3 = w2;
    copy构造函数是一个尤其重要的函数,因为它定义一个对象如何passed by value,所以Pass-by-value意味差“调用copy构造函数”。

2.explicit关键字
explicit只对构造函数起作用,用来阻止隐式转换

class A {
public:
	explicit A(int x){}
};

void doSomething(A aObject);  //函数,接受一个类型为A的对象

A a(20);
doSomething(a);  //正确
doSomething(20);  //错误,使用explicit声明了构造函数,无法进行隐式转换

注意,explicit只对一个参数的构造函数有效。也有一种例外情况,就是除第一个参数外,其它参数均提供了默认参数。

3.若不想使用编译器自动生成的函数,就该明确拒绝
编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析函数。要阻止编译的这样的默认行为,可以将成员函数为private而且故意不实现它们:

class HomeForSale {
public:
    ...
private:
    ...
    HomeForSal(const HomeForSale&); //只有声明
    HomeForSale& operator=(const HomeForSale);
};

   有了上述的class定义,当客户企图拷贝HomeForSale对象,编译器会阻挠他。哪果你不慎在member函数或者friend函数之内那么做,轮到连接器发出抱怨。我们可以定义一个专门的base class,将连接期错误移至编译期:

class Uncopyable{
protected:
    Uncopyable() {}                 //允许derived对象构造和析构
    ~Uncopyable() {}
private:
    Uncopyable(const Uncopyable&);  //但阻止copying
    Uncopyable& operator=(const Uncopyable&);
}

    为阻止HomeForSale对象被拷贝,我们唯一需要做的是继承Uncopyable。当尝试拷贝HomeForSale对象,编译器便试着生成一个copy构造函数和一个copy assignment操作符,这些函数的“编译器生成版”会尝试调用其base class的对应兄弟,那些调用会被编译器拒绝,因为其base class的拷贝函数是private。boost中也提供了这样的base class,名为noncopyable。

4.pure virtual析构函数
有时候希望拥有一个抽象class,但是没有任何pure virtual函数,怎么办?这时,可以声明一个pure vitual析构函数。下面是个例子:

class AWOV {
public:
    virtual ~AWOV() = 0;    //声明pure virtual析构函数
}

注意一个问题,你必须为这个pure virual析构函数提供一份定义:
AWOV::~AWOV()   //pure virtual析构函数的定义
编译器会在AWOV的derived class的析构函数中创建一个对~AWOV的调用动作,所以你必须为这个函数提供一份定义。否则,连接器会发出抱怨。

5.别让异常逃离析构函数
析构函数中若抛出异常,可能会导致资源没有释放完全。析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或者结束程序。还有一个解决办法,提可能抛出异常的操作放到一个普通函数中,让客户有机会调用。

6.绝不在构造和析构过程中调用virtual函数
base class 构造期间调用的virtual函数,其实现调用的是base class中的版本,因为此时derived class根本还没有进行构造,怎么能调用其中的函数呢?所以对象在derived class构造函数开始执行前不会成为一个derived class对象。相同道理也适用于析构函数。

7.在operator=中处理“自我赋值”
“自我赋值”发生在对象被赋值给自己时:
class Widget { ... };
Widget w;
...
w = w;  //赋值给自己
上面的情况很少发生,但有些代码却可能有潜在的自我赋值,例如:
a[i] = a[j];    //i和j可能相等
*px = *py;
假设你建立一个class用来保存一个指针指向一块动态分配的位图:
class Widget {
    ...
private:
    Bitmap* pb;
};
下面是一份不安全的operator=实现代码:

Widget& Widget::operator=(const Widget& rhs)
{
    delete pb;
    pb = new Bitmap(*rhs.pb);   //自我赋值时,pb已经删除了
    return *this;
}

我们可以使用“证同测试”达到“自我赋值”的检验目的:

Widget& Widget::operator=(cosnt Widget& rhs)
{
    if (this == &rhs) return *this;     //证同测试
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

这样做行得通,但是存在异常方面的麻烦,如果"new Bitmap"导致异常,问题就出现了。其实还有一种办法,我们只需要注意在复制pb所指东西之前别删除pb:

Widget& Widget::operator=(cosnt Widget& rhs)
{
    Bitmap* pOrig = pb;
    pb = new Bitmap(*rhs.pb);
    delete pb;
    return *this;
}

省去了证同测试。

8.关于自定义的copy构造函数和copy assignment操作符
自定义derived class的copy构造函数和copy assigment操作符时,切记要调用base class的相应函数,否则将导致base class部分自制不完全。 

摘自Effective C++

PS :

1.没有默认构造函数的类作为另一个类的成员时,只能(必需)在构造函数中对其进行初始化。

抱歉!评论已关闭.