首页 > 编程语言 > 详细

effective C++之构造、析构、赋值运算

时间:2015-03-22 22:27:21      阅读:193      评论:0      收藏:0      [点我收藏+]

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

   编译器会声明一个copy构造函数,一个copy assignment 操作符合一个析构函数所有这些函数都是public和inline的

  如果打算在一个内含reference 成员的class内支持赋值操作你必须自己定义copy assignment 操作符面对const成员的classes编译器反应也一样。

   另一种情况:如果某个base class将assignment操作符声明为private编译器将拒绝为derived class 生成一个copy assignment 操作符

请记住

@编译器可以暗自为class 创建default构造函数,copy构造函数,copy assignment 操作符,以及析构函数

条款06 如果不想使用编译器自动生成的函数就应该明确的拒绝

有个class 用来描述待售房

class HomeForSale{};

任何一笔资产都是独一无二的那么该class不该存有副本

HomeForSale h1;

HomeForSale h2;

HomeForSale h3(h1)//企图拷贝h1  ----编译不应该通过

h1=h2;/编译不应该通过

为解决以上问题

可以讲copy构造函数和copy assgiment 操作符声明为private来阻止编译器暗自创建其专属版本。但这也不是绝对的安全因为member函数和friend函数还是可以调用你的private函数。最好的方法是不去定义他们即将成员函数声明为private而且故意不提供实现他们。

那么HomeForSale可以这样的改写

class HomeForSale{

    private:

   HomeForSale(const HomeForSale&);//只是声明不去实现

   HomeForSale& operator=(const HomeForSale&);//同上

};

很多情况下我们专门设计一个阻止copying动作的base class像这样

class Uncopyable{

   proctected:

    Uncopyable(){}

   ~Uncopyable(){}

private:

   Uncopyable(const Uncopyable&);

   Uncopyable& operator=(const Uncopyable&);

};

其他类只需继承此类便可获取阻止copying行为(boost 库提供 class noncopyable来实现阻止copying行为)

请记住:

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

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

class TimeKeeper{

 public:

    TimeKeeper();

~TimeKeeper();

};

class AtomicClock:public TimeKerrper{...}

class WaterClock:public TimeKeeper{...}

我们这样的到对象指针:

TimeKeeper* getTimeKeeper();//指向一个派生类动态分配的对象指针

TimeKeeper*ptk=getTimeKeeper();

delete ptk;

以上会导致一些问题C++明白的指出当derived对象经由一个base class 指针删除时而该base class带着一个non-virtual 析构函数那么结果是未有定义的通常不会释放derive的成分

消除问题很简单为base class 提供一个virual 析构函数

但如果class 不含vitual函数那么通常情况下他并不意图被当做一个base class 如果令其析构函数为virtual 那么行为也将是糟糕的因为含有virtual函数的对象其内部维护了一个vptr指针其对象将会比一般不带virual函数的对象要大

  还有一个问题需注意:

class SpecialString:public string//string有个non virual析构函数

{

 

};

如果你在程序中无意间将一个pointer to SpecialString转化为一个pointer  to string然后delete掉,就会出现行为不明确

string有个non virual析构函数

有时候让class带一个pure virtual 析构函数可能会更加的便利(同时要为此析构函数提共一份定义否则编译器会抱怨的)

class AWOV

{

  public:

  virtual ~AWOV()=0;

};

AWOV::~AWOV(){}

上面这种情况只适用于带多态性质的base class 身上


请记住:

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

@Classes 的设计目的如果不是作为base class 适用,或者不是为了具备多态性质就不该声明为virual析构函数

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

C++不鼓励你在析构函数中吐出异常

如果你的析构函数必须执行一个动作而该动作可能会在失败的时候抛出异常例如:

class DBConnection{

   public:

  static DBConnection Create();

  void close();

}

为了 确保客户在DBConnection对象身上调用close()一个合理的想法是创建一个用来管理BDconnection资源的class

class DBConn

{

   ~DBConn()

  {db.close()}

   private:

   DBConnection db;   

}

close()调用成功一切美好,但如果该调用导致了异常DBConn会将该异常传播也就是允许他离开该析构函数那么就会造成问题.

我们通常有一下方式改善

1 如果close抛出异常就结束程序通常用abort完成

DBConn::~DBConn()
{
try
{
db.close()
}
catch (...)
{
std::abort();
}
}

强制结束程序

2 吞下因调用close而发生的异常

DBConn::~DBConn()
{
try{ db.close(); }
catch (...)

..........
}
}

不管这个异常仍然继续执行

3最佳的做法是重新设计DBConn接口使用户有机会在对可能出现问题作出反应如下

class DBConn
{


public:
//...
void close()//供客户使用,是客户有一次机会捕获异常
{
db.close();
close = true;
}
~DBConn()
{
if (!close)
{
try{ db.close(); }
catch (...)
{
//记录异常 关闭程序或者吞下异常
}
}
}
private:
DBConnection db;
bool close();
};

请记住:

@ 析构函数绝对不要吐出异常如果一个被析构函数调用的函数可能抛出异常析构函数应该捕获任何异常,然后吐下他或者结束程序

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

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

假如你有以下class继承层次

class Transaction

{

   public:

     Transaction();

     virtual void LogTransaction() const =0;//因为类型不同日志记录也会不同

   ....

};


Transaction::Transaction(){

LogTransaction();

}

class BuyTransaction:public Transaction{

public:

virtual void LogTransaction()const;//交易日志Buy

}

class SellTransaction:public Transaction

{


public:

virtual void LogTransaction()const;//交易日志Sell


}

假如有以下语句 BuyTransaction那么LogTransaction此时调用的版本将是基类中的版本并不是BuyTransaction内的版本,base class 构造期间内的virtual 函数绝对不会下降到derived class阶层(在base class构造期间virtual函数不是virtual函数)

一种解决方法:在class Transaction 内将logTransaction函数改为non-virtual 函数然后要求derived class构造函数传递必要的信息给logTransaction构造函数

clss Transaction{

public:

explict Transaction(const std::string& loginfo);

void LogTransaction(const std::string& loginfo) const ;

};

Transaction::Transaction(const std::string& loginfo)

{

   ....

   logTransaction(loginfo)

}

class BuyTransaction::public Transaction

{

  public:

  BuyTransaction(prameter):Transaction(CreateString(praneter)){...}

private:

   string CreateString(parameters)

}

请记住:

@在构造函数和析构函数期间不能调用virtual函数因为这类调用从不下降至derived class

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

形如:Widget& operator=(const Widget&rhs)

{

 return *this;

}

请记住:

令赋值操作符返回一个reference to *this;

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

假如你建立一个class用来保存一个指针指向动态分配的位图(bitmap)

class Bitmap{...}

class Widget{

private: Bitmap* pb;

}

operator=的实现代码:

传统方法是加入证同测试达到自我赋值的检验目的

Widget& Widget::operator=(const Widget& rhs)

{

    if(this==&rhs)

         return *this;

   delete pb;

  pb=new  BitMap(*rhs.pb);

  return *this;

}

如果你很关心程序效率 ,可以再次把证同测试放到函数开头,不过你有大致知道自我赋值的频率到底有多大因为证同测试同样需要成本
同样我们可以通过“copy and swap”技术来替代手工排序代码解决自我赋值的问题
class Wiget{};
void swap(Widget& rhs)
{
.......
}
Wiget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs);
    swap(temp);
    return *this;
}
更高效的代码:(基于1:某class的 copy assigment操作符可以被声明为以by value方式接受参数2:以by value方式传递东西会造成一份复件)
Wiget& Widget::operator=(Widget rhs)
{
    
    swap(rhs);
    return *this;
}

请记住:
@确保对象自我赋值时operator=有良好的行为,其中技术包括比较“来源对象”和“目的对象”的地址、精心周到的语句顺序,以及 copy-and-swap
@确定任何对象操作一个以上的对象,而其中多个对象时同一个对象时,其行为仍然正确


条款12 赋值对象时勿忘赋值对象的每一个成分


 class Date{};
 class A{
   public :
   A(const A&);
   A& operator(const A&a);
   private:
   string   m_strname;
   Date     m_Date;
 };
不使用编译器提供的构造函数不要忘记复制对像的每个成分
class B:public A
{
     public:
     B(const B& b);
     B& operator=(const B& b);
     int m_p;
};

B::B(const B&b):A(B),m_p(b.m_p)//调用A的构造函数
{
  logcall("copying");
}
B& B::coperator=(const B& b)
{
   logcall("copying assignment");
   A::operator=(B);
   m_p=b.m_p;
   reutrn *this;
}
让 copy 构造函数调用 copy assignment操作符 和让copy assignment 操作符调用 copy构造函数两者都不会有意义!
若两个有相近的代码产生则通过引入第三个函数解决来降低代码重复
请记住:
@copying 函数应该确保赋值“对象的所有成员变量”有有base class 成分
@不要尝试以某个copying函数实现另一个copying函数,应该将共同的机能放进第三个函数中并由两个函数共同调用










effective C++之构造、析构、赋值运算

原文:http://blog.csdn.net/luguifang2011/article/details/42884205

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!