构造,析构,赋值运算

构造,析构,赋值运算

每一个函数都有 构造函数,析构函数,copy assignment操作符。控制初始化,摆脱对象时的清理工作。确保它们的行为正确非常重要。本章节提供把这些函数良好的集结起来的方式。

Rule 05 了解C++默认编写并且调用了哪些函数

当创建一个空类,经过C++处理过后,在自己没有声明的情况下编译器就会为它声明一个copy构造函数,一个copy assignment操作符和一个析构函数。若没有声明任何构造函数,也会声明一个default构造函数。所有函数都是public inline.

特别讨论copy构造函数和copy assignment operator.只有当代码合法且有适当机会证明它有意义。否则编译器拒绝生成。

eg:

template<class T>
class NamedObject { 
    NameObject(string& name, const T& name);
private:
    string& nameValue;
    const T objectValue;
}

string newDog("Persephone");
string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 36);
p = s;

这里定义不清晰。因为nameValue是一个reference.那么到底是p.nameValue指向s.nameValue.还是把p.nameVaule的值改掉。

  1. C++不允许reference改指到别的地方。

  2. 改值后会影响其他指向改值的对象。

C++在这里拒绝编译赋值操作。

  • 编译器可以暗自为class创建default构造函数,copy构造函数,copy assignment以及析构函数。

Rule 06 若不想使用编译器自动生成的函数,应该明确拒绝

书中对拒绝自动编译出函数举了例子。

每一位真正的地产中介商都会说,任何一笔资产都是天上地下独一无二,没有两笔完全相像。因此我们也认为,为HomeForSale对象做一份副本有点没道理。你怎么可以复制某些先天独一无二的东西呢?因此,你应该乐意看到HomeForSale的对象拷贝动作以失败收场.

HomeForSale hl;
HomeForSale h2;
HomeForSale h3(hl);//企图拷贝hl一不该通过编译
hl=h2;//企图拷贝h2一也不该通过编译

拒绝拷贝:

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

class Uncopyable{
protected://允许derived对象构造和析构
    Uncopyable(){)
    }Uncopyable(){}
private:
    Uncopyable(const Uncopyable&);//但阻止copying
    Uncopyable& operator=(coast Uncopyable&);
};
//为求阻止HomeForSale对象被拷贝,我们唯一需要做的就是继承Uncopyable:
class HomeForSale: private Uncopyable{
//class不再声明
//copy构造函数或
//copy assign.操作符
};
  1. 把函数声明为private函数并且不去实现它。

  2. 生成一个Uncopyable作为父类,把父类的相应函数设置为private。当子类调用相应操作时回去调用父类版本,但是此时父类声明为private无法实现,编译报错。

  • 为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

Rule 07 为多态基类声明virtual 析构函数

考虑这种情形,Base pointer指向一个Derived class 对象。如果这个时候调用Delete。在Base Class有析构函数的情况下,调用了Base的析构函数,而子类的部分没有被析构掉。造成"局部析构"的现象。

标准:

  • 当类中有virtual函数(基类),通常会把析构函数声明为virtual。

  • 当类不被当做基类使用,声明析构函数为virtual是坏主意。

attention:

有些库类不是虚析构函数。在继承的时候如果转为Base指针,Delete的时候就会出问题

乍看似乎无害,但如果你在程序任意某处无意间将一个pointer-to-SpecialString。转换为一个pointer-to-string,然后将转换所得的那个string指针delete掉,你立刻被流放到“行为不明确”的恶地上:

SpecialString* pss=new SpecialString("Impending Doom");
std::string* ps;
ps=pss;//未有定义!现实中*ps的Specials七ring资源会泄漏,
//SpecialString=>std::string;
delete ps;//因为SpecialString析构函数没被调用。


  • polymorphic(带多态性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。

  • Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性(polymorphically ),就不该声明virtual析构函数。

Rule 08 别让异常逃离析构函数

面对析构函数的异常通常有两种做法,一种是抛出异常。一种是abort让程序挂死。但是为了程序能够正常运行,比如析构一个数组,不能因为其中一个析构失败而终止所有的析构。

较好的方式:

首先把析构函数的内容封装到一个函数中提供给客户,交由客户处理。再增加判断,若客户不处理的情况下析构函数处理。

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

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

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

在调用Base构造函数时,看不到derived阶层,因为其中的许多变量未初始化,不应该下降到derived层。

但是侦测“构造函数或析构函数运行期间是否调用virtual函数”并不总是这般轻松。如果Transaction有多个构造函数,每个都需执行某些相同工作,那么避免代码重复的一个优秀做法是把共同的初始化代码(其中包括对logTransaction的调用)放进一个初始化函数如init内:

class Transaction{
public:
Transaction()
{init();}
virtual void logTransaction()const = 0;
private:
void init()
logTransaction();
}


  • 在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)。

Rule 10 令operator= 返回一个reference to *this

主要是为了遵从内置的类型,保持行为一致。

关于赋值,有趣的是你可以把它们写成连锁形式:int x,y, z;x = y = z;同样有趣的是,赋值采用右结合律,所以上述连锁赋值被解析为:x=(Y=(z=15));

为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参。这是你为classes实现赋值操作符时应该遵循的协议.

  • 令赋值(assignment)操作符返回一个reference to *this.

Rlue 11 在Operator= 中处理“自我赋值”

存在这种情况:

class Widget {};Widget w;w = w;

如果operator=实现版本如下:

class Bitmap{...};
class Widget
private:
    Bitmap* pb;//指针,指向一个从heap分配而得的对象
};
Widget&
Widget::operator=(const Widget& rhs)//一份不安全的。perator一实现版本
{
  delete pb;//停止使用当前的bitrnap
  pb=new Bitmap(*rhs.pb);//使用rhs's bitmap的副本(复件)。
  return *this;//见条款l00
}

如果两个对象相同,以上代码先把pb delete,pb已经无意义然后再去取值。错误。

Widget& Widget::operator=(const Widget& rhs)
{
   if(this == &rhs) return *this;//证同测试(identity test):
  //如果是自我赋值,就不做任何事。
  delete pb;
  pb = new Bitmap(*rhs.pb);
  return *this;
}

还是没有解决new 失败的问题:


widget& Widget::operator=(const Widget& rhs)
{
    Bitmap* pOrig = pb;         //记住原先的pb
    pb = new Bitmap(*rhs.pb);//令pb指向*pb的一个复件(副本)
    delete pOrig;//删除原先的pb
    return *this;
}


  • 确保当对象自我赋值时operator一有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap.

  • 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

Rule 12 复制对象时勿忘其每一个成分

设计良好之面向对象系统(OO-system)会将对象的内部封装起来。只留两个负责函数负责对象拷贝(复制 copy构造函数 copy assignment操作符)。

当自己声明了自己的copying函数,编译器不回去检查是否复制完全。

void logCall(const std::string& funcName);
class Customer{
public:
//制造一个log
Customer(const Customer& rhs);.
Customer& operator=(const Customer& rhs);
private:
    std::string name;
};

Customer::Customer(const Customer& rhs)
  :name(rhs.name)
{//复制rhs的数据
    logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs)
{
    logCall("Customer copy assignment operator");
    name = rhs.name;//复制rhs的数据
    return *this;//见条款10

}

如果再添加成员变量lastTransaction:

class Date {//日期
class Customer{
public:
...//同前
private:
    std::string name;
     Date lastTransaction;
}

这些必须自己保证,编译器不做此检测。

  • 当继承时,Derived copy函数必须调用父类的copy函数。

  • 在copying函数中,两个函数的实现相近,切不可copy构造函数调用copy assignment,反之亦不可。可以尝试在封装在一个函数中,两者去调用。

  • Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。

  • 不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值