effective C++ 学习(Constructors, Destructors, and Assignment Operators)
Item 06: Explicitly disallow the use of compiler-generatedfunctions you do not want.
1. If you do not want to use thecompiler-generated functions, you can declare those as private functions, anddo not define these.
2. If you do not also allow memberfunctions and friend functions to call these functions, you can achieve thegoal by base class.
class Uncopyable
{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(constUncopyable&);
Uncopyable& operator= (const Uncopyable&);
};
class HomeForSale: privateUncopyable
{...
};
Item 07: Declare destructors virtual in polymorphic(多态) base classes.
1. If you want to delete a derivedclass by a base class pointer, you must declare destructors virtual, otherwiseonly the base class part of it is destructed. Because the virtual functionsachieve polymorphic. So we should use virtual destructors, so long as there isat least a virtual function in base class.
2. If you do not want to use it asa base class, you should not declare destructors virtual, because a classcontaining virtual functions should maintain a virtual table pointer(vptr) usedto call the right virtual functions, which increases the storage space.
3. If you derive a new class onthe class without virtual destructors, it will lead to error in 1.
4. There are some base classesbeing designed not to polymorphic, so they do not need virtual destructors.
5. If you need a pure virtualfunction, a pure virtual destructor will be a good choice. But you should notforget to define the virtual destructor, because it could be called whencompiler destructs its derived classes.
class AWOV
{
public:
virtual~AWOV()=0;
};
AWOV::~AWOV(){}
class Tawov:public AWOV
{
};
int main()
{
Tawov test;
6. Destructing a derived classes,compiler firstly destructs the most derived classes, then deletes their baseclasses.
Item 08: Prevent exceptions from leaving destructors.
1. The number of exceptions ismore than 1, it will lead to an uncertain errors.
2. The exceptions thrown fromdestructors should be aborted or be only recorded. But they are not the bestchoices.
3. If users need to deal withthese exceptions, you should use a common function to process exceptions butnot destructors. This may be a better choice.
class DBConn
{
public:
void close()
{
db.close();
closed = true;
}
~DBConn()
{
if(!closed)
{
try
{
db.close();
}
catch(...)
{
recording the fail calling close();
}
}
}
private:
DBConnection db;
bool closed;
};
Item 09: Never call virtual functions during constructionor destruction.
1. Never call virtual functionsduring construction or destruction, because the virtual functions never drop toderived classes, in which the reasons are the following points:
1) During constructing baseclasses, the derived classes are not yet constructed and their member variablesare not initialized.
2) Compiler also selects the baseclasses according to running type information (dynamic_cast).
2. The same reasons not to callvirtual functions during destruction, because the members in derived classeshave been released during destruction, the virtual functions point baseclasses.
class Transaction
{
public:
explicitTransaction(const std::string& logInfo);
voidlogTransaction(const std::string& logInfo) const;
};
Transaction::Transaction(const std::string & logInfo)
{
logTransaction(logInfo);
}
class BuyTransaction:publicTransaction
{
public:
BuyTransaction(conststd::string& parameters)
:Transaction(createLogString(parameters)){}
private:
staticstd::string createLogString(conststd::string& parameter);
};
Note: where we use non-virtual function andstatic function to achieve our goal. Even if the derived classes are notconstructed, we can also call the static function.
Item 10: Have assignment operators return a reference to*this.
1. This is an agreement but not arequirement. But you’d better follow it.
class Widget
{
public:
Widget& operator=(const Widget& rhs)
{
...
return *this;
}
Widget& operator+=(const Widget& rhs)
{
...
return *this;
}
};
Note: we can achieve successive assignmentby this way.
Item 11: Handle assignment to self in operator=.
1. The following five versions:
class Bitmap{};
class Widget
{
public:
Widget& operator=(const Widget& rhs);
private:
voidswap(Widget& rhs);
Bitmap* pb;
};
Widget&Widget::operator=(constWidget& rhs)
{
delete pb;
pb = newBitmap(*rhs.pb);
return *this;
}
Widget&Widget::operator=(constWidget& rhs)
{
if(this == &rhs) return*this;
delete pb;
pb = newBitmap(*rhs.pb);
return *this;
}
Widget&Widget::operator=(constWidget& rhs)
{
//if(this ==&rhs) return *this; // if the cases of assignment
//to self are many,adding the sentence is a good choice,
//otherwise, itwill increase cost.
Bitmap* pOrig = pb;
pb = newBitmap(*rhs.pb);
delete pOrig;
return *this;
}
Widget&Widget::operator =(constWidget& rhs)
{// copy and swap trick
Widget temp(rhs); //copying
swap(temp); //swaping
return *this;
}
Widget&Widget::operator=(Widget rhs)
{//moving the copying to parameter location from function;
swap(rhs);
return *this;
}
1) The first version must not beused. Because if the assignment is to itself, the object that should not havebeen released is released.
2) As to the second version, ifnew Bitmap(*) take place exception, the “pb” will be a NULL, prematurely(过早地) release the object, and it will lead to break out.
3) The third version is a rightcase, but its efficiency may be not the best. We can select whether to add the judgmentof itself according to frequency of assignment to self.
4) Achieving the assignment toself by copy and swap trick. The is a good case.
5) The fifth version is the sameas the fourth, and it only move the copying to the parameter location.Sometimes, the compiler can make the case get better efficiency, but it losesthe clarity.
Item 12: Copy all parts of an object.
1. Copying function should ensurecopy all parts of an object including the members in base class;
class Customer
{
public:
Customer():m_name("null"){}
Customer(conststd::string& name)
:m_name(name)
{
}
Customer(constCustomer& customer)
:m_name(customer.m_name)
{
}
Customer& operator=(const Customer& customer)
{
m_name = customer.m_name;
return *this;
}
private:
std::string m_name;
};
class PriorityCustomer: publicCustomer
{
public:
PriorityCustomer(std::string name, int priority)
:Customer(name),m_priority(priority)
{}
PriorityCustomer()
:m_priority(0){}
PriorityCustomer(constPriorityCustomer& pcustomer)
:Customer(pcustomer),m_priority(pcustomer.m_priority)
{}
PriorityCustomer& operator=(const PriorityCustomer& pcustomer)
{
m_priority = pcustomer.m_priority;
Customer::operator=(pcustomer);
implicit conversions occurs
return *this;
}
private:
int m_priority;
};
int main()
{
PriorityCustomer pCustomer1("yang", 1), pCustomer2(pCustomer1),pCustomer3;
pCustomer3 = pCustomer1;
return 0;
}
Note: Under no appoint out theconstructors, the constructors of derived classes only call the defaultconstructors of base classes. Therefore, we should write out the rightconstructors of base classes to ensure the members in base classes to be updatedin assignment operators or copying constructors.
2. The assignment operator andcopy constructor are banned from calling each other. Because copy constructoris to initialize the object not initialized, but assignment operator is to dealwith the object initialized. If we call the assignment operator in copyconstructor, lead to the phenomenon that the uninitialized objects are used. Ifthe copy constructors are called in assignment operator, it may reinitializethe objects that have been initialized. If we want to reduce duplicated code,we can rewrite a function containing these codes, called by assignmentoperators and copy constructors.