您的位置:首页 > 编程语言 > C语言/C++

Effective C++ 改善程序与设计的55个具体做法 二周目笔记02

2015-09-15 17:49 357 查看
第二部分构造/析构/赋值运算

一个由c/c++编译的程序占用内存分为以下五个部分:

栈区—由编译器分配释放,存放函数的参数值,局部变量的值等。

堆区—由程序员申请和释放,若程序员不释放,程序结束后由操作系统回收。

全局(静态)区—全局变量和静态变量一起存储。初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量存储在另一块区域。程序结束后,由系统释放。

文字常量区—常量字符串的存放位置。程序结束后由系统释放。

程序代码区—存放程序的二进制代码。

条款05:了解C++默认编写并调用哪些函数

如果程序员没有声明,则编译器会声明的函数包括:

Default构造函数、copy构造函数、copyassignment操作符、析构函数。

所有这些函数都是public且inline。(想想为什么?)

这些函数会在不自觉的时候被调用,是基本的谋生工具。

编译器提供的copy构造函数和copyassignment操作符,只是单纯地将源对象的每一个non-static成员变量拷贝到目标对象。(对象相关的一般都是non-static成员变量,因为static变量不是成员变量,它是属于整个类的,不属于任何一个对象)。面对内置类型的成员变量,编译器会以拷贝源对象内的变量的每一个bits来完成目标对象的内变量的初始化。



成员变量的初始化在声明之后调用构造函数之前。

class base

{

public:

         base(string &thename, int x) :name(thename), years(x)

         {

         

         }

private:

         //默认operator=不可用原因:

         //1.编译器不确定不同对象中的引用变量是否可以指向同一个数据

         //2.常量不可赋值

         string& name;

         const int years;

};

如果你打算在一个内含reference成员或者内含const成员的class内支持赋值,你必须自己自己定义copy
assignment操作符。

如果某个baseclasses将copyassignment操作符声明为private,编译器将拒绝为所生成的derived
classes生成copyassignment操作符。

条款06:若不想使用编译器自动生成的函数,就该明确拒绝

为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。

例如:将base类的copy构造函数和copyassignmet操作符声明为private,并且不予实现,可以阻止copying。凡是继承base的derivedclasses也都阻止了copying。(思考采用什么继承方式好呢?是is-a还是has-a还是is-inplemented-in-term-of)

class uncopyable

{

public:

         //在私有部分声明了copy构造函数,编译器便不会再提供任何构造函数

         uncopyable() {};

private:

         //uncopyable的derived classes也不可复制

         uncopyable(const uncopyable&);            //阻止copying

         uncopyable operator=(const uncopyable&);  //阻止copying

};

条款07:为多态基类声明virtual析构函数

“给baseclasses一个virtual析构函数”,这个规则只适用于polymorphicbaseclasses(多态性质的基类)。这种base
classes的设计目的是为了用来”通过baseclasses接口处理derivedclasses对象“。

class base

{

   ······

};

 

class derived : public base

{

         ······

};

 

int main()

{

         base *p = new derived();

         delete p;//如果base的析构函数是non-virtual

                               //这是一个未定义行为

                               //通常可能发生的结果是对象的derived部分没被销毁

                               //形成资源泄露,败坏数据结构

}

并非所有baseclasses的设计目的都是为了多态用途。例如标准string和STL容器都不被设计作为base
classes使用,更别提多态了。某些classes的设计目的是作为baseclasses使用,但不是为了多态用途。这样的classes例如uncopyable和标准程序库中的input_iterator_tag,它们并非被设计用来“经由base
class接口处置derivedclass对象“,它们被继承往往是为了实现某些功能,而不是重新实现或者变化或丰富某些功能。以上所列这些不需要virtual析构函数。

条款08:别让异常逃离析构函数

析构函数函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。

如果客户需要对某个操作函数运行期抛出的异常做出反应,那么class应该提供普通函数(而非在析构函数中)执行该操作。

条款09:绝不在构造函数和析构过程中调用virtual函数

#include <iostream>

using namespace std;

 

class  base

{

public:

         base() 

         {

                   display();

         }

         virtual void display() = 0

         {

                   cout <<"this is base " << endl;

         }

         void father_use_son_func()

         {

                   display();

         }        

         ~base()

         {

                   display();

         }

};

 

class derived :public base

{

public:

         derived()

         {

                   display();

         }

         void display()

         {

                   cout << "this is derived " << endl;

         }

         ~derived()

         {

                   display();

         }

};

int main()

{

         {                                                                                                                       //this is base

                   derived one;             //this is derived

                   one.father_use_son_func(); //this is derived                                                            //this is derived

         }                              //this is base

                                                                                          

         cin.get();

}

base构造函数执行起来打算初始化derived对象中base成分时,该对象的类型是base。同理,一旦derived析构函数开始执行,对象内的derived成员变量便呈现未定义值,所以C++视它们仿佛不存在。进入base析构函数之后,对象就成为一个base对象。

在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived(亦即在构造和析构期间,virtual函数不是virtual)。

在构造期间,如果base需要根据derived信息构造某些部分,采用virtual函数是行不通了,可以在base构造时,将一些derived的相关参数传递上去(令derived将必要的构造信息上传至base)。

条款10:令operator=返回一个referenceto*this

为了实现“连锁赋值”,复制操作符必须返回一个指向操作符左侧实参reference。

就像是加勒比海盗中的帕雷协议,条款10只不过是一条guideline,它并不是必须遵守的规定。然而这份协议被所有内置类型和标准程序库提供的类型如string,vector,complex,tr1::shared_ptr或者即将提供的类型共同遵守。

条款11:在operator=中处理”自我赋值“

 拷贝构造函数和assignment操作符的区别。

如果你尝试编写自行管理资源的class,那么自我赋值可能会变得不安全,因为你有可能落入“在停止使用资源之前,已经将其释放”的陷阱。

例如:

class bitmap
{
······
};
class widget
{
public:
         widget& operator=(const widget& rhs);
private:
         bitmap * pb;
};
widget& widget::operator=(const widget& rhs)
{
         delete pb;
         pb = new bitmap(*rhs.pb);//此时,如果自我赋值,将会发现rhs.pb所指bitmap对象已经被销毁。
         return *this;            //另外,此时如果new bitmap导致异常,widget对象最终会持有一个指针
                                //指向一块被删除的bitmap。故不具备异常安全性。
}

传统做法:添加证同测试。

widget& widget::operator=(const widget& rhs)
{
         if (&rhs == this) return *this;//证同测试
         delete pb;
         pb = new bitmap(*rhs.pb);//此时,如果自我赋值,将会发现rhs.pb所指bitmap对象已经被销毁。
         return *this;            //另外,此时如果new bitmap导致异常,widget最终会持有一个指针
                                //指向一块被删除的bitmap。故不具备异常安全性。
}


自我赋值的几率太小的话,证同测试就会浪费很多成本。

下面是另外一种做法,免去了证同测试,同时保证异常安全性:

widget& widget::operator=(const widget &rhs)
{         bitmap * pOrig = pb;       //保存原始对象         pb = new bitmap(*rhs.pb);  //创建一个新的对象 ,如果此时异常,原始对象不会被删除         delete pOrig;           //删除原始的对象         return *this;}

在operator=函数内手工排列语句(确保代码“异常安全性“和”自我赋值安全“)的另一个替代方案是copyandswap技术。(关于swap函数,详见条款
25:考虑写出一个不抛出异常的swap函数)
假设此时我们有一个安全的swap函数,我们可以这样做:

class bitmap {};

class widget

{

public:

         void swap(widget& rhs);//安全地交换*this和swap的内容

         widget& operator=(const widget& rhs);

         widget& operator=(widget rhs);

private:

         bitmap * pb;

};

widget& widget::operator=(const widget &rhs)
{

         widget temp(rhs);//operator=不应该改变rhs内容,故先生成一个rhs副本

         swap(temp);

         return *this;

}

widget& widget::operator=(widget rhs)

{

         swap(rhs);//此时的rhs是实参的一个副本

         return *this;

}

条款12:复制对象时,不要忘记其每一个成分

当你编写一个copying函数,请确保:

复制所有non-static成员变量;

调用所有baseclasses内适当的copying函数,因为你将要处理的很可能都是baseclasses的private成员变量。

例如:

class base
{
         ``````
};
class derived : public base
{
public:
         derived(const derived & rhs);
         derived &operator=(const derived &rhs);//必须是成员函数
};
derived::derived(const derived & rhs) :base(rhs)
{
         ``````
}
derived &derived::operator=(const derived &rhs)
{
         base::operator=(rhs);
         `````
         return *this;
}


不要尝试以某个copying函数实现另一copying函数。应该将共有的部分提取出来放在一个函数中(一般设置为看private,函数名init),并由两个函数共同调用。

Copyassignment操作符调用copy构造函数就像试图构造一个已经存在的对象。

Copy构造函数调用copyassignment操作符就像试图调用一个已经构造完成的对象(copyassignment操作符只是使用于已经初始化的对象上)。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++