preface
Almost every class we write will have one or more constructors, a destructor and a copy assignment operator. It's hard to be particularly excited about these. After all, they are your basic tools for making a living, controlling basic operations, such as producing a new object and ensuring that it is initialized, getting rid of the old object and ensuring that it is properly cleaned up, and giving new values to the object. If these functions make mistakes, they can lead to far-reaching and unpleasant consequences throughout our entire class. So making sure they behave correctly is a matter of life and death. The guidance provided in this chapter allows us to put these functions together well to form the spine of classes.
Clause 05: understand which functions C + + silently writes and calls
class Empty { };
Equivalent to the following classes:
class Empty { public: Empty(){} //default constructor Empty(const Empty&rhs){} //Default Copy Constructor ~Empty(){} //Default destructor //virtual, see you later Empty& operator=(const Empty&rhs){}//copy assignment operator };
class Empty { public: Empty(){...} //default constructor Empty(const Empty&rhs){...} //Default Copy Constructor ~Empty(){...} //Default destructor //virtual, see you later Empty& operator=(const Empty&rhs){...}//copy assignment operator };
Since a constructor is declared, the compiler no longer creates a default constructor for it. This is important ,It means that we design a class carefully class,Its constructor requires arguments, so we don't have to worry about compilation The constructor will not worry about adding a parameterless constructor to you and masking your version.
template<typename T> class NameObject { public: NamedObject(const char*name, const T&value); NamedObject(const std::string&name, const T&value); private: std::string nameValue; T objectValue; };
template<typename T> class NamedObject { public: //The following constructor no longer accepts a const name because of nameValue //Now it's a reference to non const string. The previous char * constructor //It's over because there must be a string to refer to. NamedObject(std::string& name, const T&value); //... It is assumed that the operator is not declared= private: std::string& nameValue; //This is now a reference const T objectValue; //This is a const now };
Now consider what happens:
template<typename T> class NamedObject { public: //The following constructor no longer accepts a const name because of nameValue //Now it's a reference to non const string. The previous char * constructor //It's over because there must be a string to refer to. NamedObject(std::string& name, const T&value); //... It is assumed that the operator is not declared= private: std::string& nameValue; //This is now a reference const T objectValue; //This is a const now }; std::string newDog("Persephone"); std::string oldDog("Satch"); NamedObject<int> p(newDog, 2); NamedObject<int> s(oldDog, 36); p = s;
please remember
- The compiler can secretly create default constructor, copy constructor, copy assignment operator and destructor for class.
Clause 06: if you don't want to use functions automatically generated by the compiler, you should explicitly refuse
All compiler generated functions are public. To prevent these functions from being created, we declare them ourselves They are, but there are no requirements here that require you to declare them as public. So you can copy Constructor or copy assignment Operator declared as priate.
class HomeForSale { public: ... private: ... HomeForSale(const HomeForSale&); //Only declaration HomeForSale& operator=(const HomeForSale&); };
If you accidentally member Function or friend Function, it's the connector's turn to complain.
class Uncopyable { protected: Uncopyable(){} //Allow derived objects to be constructed and destructed ~Uncopyable(){} private: Uncopyable(const Uncopyable&); //But stop copying Uncopyable& operator=(const Uncopyable&); };
To stop HomeForSale The object is copied, and the only thing we need to do is inherit Uncopyable:
class HomeForSale :private Uncopyable { ... };
please remember
- In order to reject the function automatically provided by the compiler, the member function of the response can be declared as private and not implemented. It is also a practice to use base class es like Uncopyable.
Clause 07: declare a virtual destructor for a polymorphic base class
class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); //... }; class AtomicClock:public TimeKeeper{}; //atomic clock class WaterClock :public TimeKeeper{}; //water clock class WristWatch :public TimeKeeper{}; //Wristwatch
//Dynamic allocation object of TimeKeeper derived class TimeKeeper* getTimeKeeper(); //Returns a pointer to a
For compliance factory The rules of the function are getTimeKeeper()The returned object must be in heap. Therefore, in order to avoid leaking memory and other resources, the factory Each object returned by the function is appropriate land delete It's important to lose.
TimeKeeper* ptk = getTimeKeeper(); //Inherit system from TimeKeeper //Get a dynamic allocation object and use it delete ptk; //Release it to avoid resource leakage
When polymorphism is used, if an attribute in a subclass is opened to the heap, the parent class pointer cannot be called when it is released The destructor code of the subclass.
The problem is that the pointer returned by getTimeKeeper points to a derived class object (such as AtomicClock), which is deleted through a base class pointer (such as a TimeKeeper * pointer), and the current base class (TimeKeeper) has a non virtual destructor.
Solution: change the destructor in the parent class to virtual destructor or pure virtual destructor.
class TimeKeeper { public: TimeKeeper(); virtual~TimeKeeper(); //... }; TimeKeeper* ptk = getTimeKeeper(); delete ptk; //Now, behave correctly
whatever class Just with virtual Functions are almost certain that there should also be one virtual Destructor If class Excluding virtual Function, usually indicating that it is not intended to be used base class.
class Point { public: Point(int xCoord, int yCoord); ~Point(); private: int x, y; };
To implement the virtual function, the object must carry some information, which is mainly used to determine which virtual function is called at run time. This information is usually indicated by a so-called vptr(virtual table pointer). vptr points to an array composed of function pointers, which is called vtbl(virtual table); Each class with a virtual function has a corresponding vtbl. When an object calls a virtual function, the function actually called depends on the vtbl referred to by the vptr of the object - the compiler looks for the appropriate function pointer.
The commonness between virtual destruct and pure virtual destruct: You can solve the problem of releasing subclass objects from parent class pointers All need specific function implementation Difference between virtual destruct and pure virtual destruct: If it is a pure virtual destructor, this class belongs to an abstract class and cannot instantiate an object
class AWOV { public: virtual ~AWOV() = 0;//Declare pure virtual destructor }; //A definition must be provided for a pure virtual destructor: AWOV::~AWOV(){} //Definition of pure virtual destructor
please remember
- Polymorphic (polymorphic) base classes should declare a virtual destructor. If class has any virtual functions, it should have a virtual destructor.
- Classes are not designed to be used as base classes or to be polymorphic, so virtual destructors should not be declared.
Clause 08: don't let exceptions escape the destructor
C++Destructors are not prohibited from throwing exceptions, but they are discouraged.
class Widget { public: ~Widget(){} //Suppose this might spit out an exception }; void doSomething() { std::vector<Widget> v; } //v is automatically destroyed here
When vector v If it is destroyed, it has the responsibility to destroy all its contents widgets. hypothesis v Including ten Widgets, An exception is thrown during the destruct of the first element. The other nine Widgets Or should it be destroyed, therefore v Their destructors should be called. But suppose that during which calls, the second Widget Deconstruction Throw an exception. Now there are two exceptions acting at the same time, which is right C++Too much for me.
class DBConnection { public: static DBConnection create(); //This function returns the DBConnection object //In order to simplify the temporary parameters, an exception will be thrown if the online shutdown fails void close(); }; class DBConn { public: ~DBConn() { db.close(); } private: DBConnection db; };
This allows the customer to write such code:
{ ... DBConn dbc(DBConnection::create()); }
Just call close Success, everything is fine, but if the call causes an exception, DBConn The destructor will The destructor allows the exception to propagate, that is, it allows the destructor to leave. That causes problems because that's throwing A difficult problem. -------------------------------------------------------------------- Two solutions can avoid this problem:
- If close throws an exception, the program ends. This is usually done by calling abort:
DBConn::~DBConn() { try { db.close(); } catch (...) { //Make operation records and record the failure of the call to close std::abort(); } }
- Swallow the exception caused by calling close:
DBConn::~DBConn() { try { db.close(); } catch () { //Make operation records and record the failure of the call to close } }
Generally speaking, swallowing anomalies is a bad idea because it suppresses them"Some actions failed"Important information. A better strategy is to redesign DBConn Interface, so that its customers have the opportunity to respond to possible problems.
class DBConn { public: void close() { //Provide new functions for users db.close(); closed = true; } ~DBConn() { if (!closed) { try { //Close connection db.close(); } catch (...) { //Make operation records and record the failure of the call to close } } } private: DBConnection db; bool closed; };
please remember
- Destructors should never spit out exceptions. If a function called by the destructor may throw exceptions, the destructor should catch any exceptions and swallow them (without propagation) or end the program.
- If a customer needs to respond to an exception thrown during the operation of an operation function, class should provide a normal (not in the destructor) to perform the operation.
Clause 09: never call the virtual function in the process of construction and destruct.
Should not be called during constructors and destructors virtual Function, because such a call will not bring the expected results.
class Transaction { public: Transaction(); virtual void logTransaction() const = 0; }; Transaction::Transaction() //Implementation of base class constructor { logTransaction(); } class BuyTransaction :public Transaction { public: virtual void logTransaction()const; }; class SellTransaction :public Transaction { public: virtual void logTransaction() const; };
please remember
- Do not call virtual functions during construction and deconstruction, because such calls never descend to derived class
Clause 10: make operator = return a reference to *this
Available when a formal parameter has the same name as a member variable this Pointer discrimination Return the object itself in the non static member function of the class, but use return *this
class Widget { public: Widget& operator=(const Widget&rhs) { //The return type is a reference //Point to current object return *this; //Return left object } };
please remember
- Make the assignment operator return a reference to *this.
Clause 11: Handling "self assignment" in operator =
class Base{}; class Derived:public Base{}; void doSomething(const Base& rb, Derived* pd);//rb and * pd may actually be the same object
please remember
- Ensure that operator = behaves well when the object is self assigned. The technologies include comparing the addresses of "source object" and "target object", careful statement order, and copy and swap.
- Determines whether any function behaves correctly if it operates on more than one object, and multiple objects are the same object.
Clause 12: don't forget every component when copying an object
class Base{}; class Derived:public Base{}; void doSomething(const Base& rb, Derived* pd);//rb and * pd may actually be the same object void logCall(const std::string& funcName); class Customer { public: Customer(const Customer&rhs); Customer& operator=(const Customer&rhs); private: std::string name; };
If you are class To add a member variable, you must modify it at the same time coying Function. If you forget, the compiler is unlikely to remind you.
Once inheritance occurs, it may lead to the most hidden crisis of this theme:
class Date{}; class Customer { public: private: std::string name; Date lastTransaction; }; class PriorityCustomer :public Customer { public: PriorityCustomer(const PriorityCustomer&rhs); PriorityCustomer& operator=(const PriorityCustomer&rhs); private: int priority; }; PriorityCustomer::PriorityCustomer(const PriorityCustomer & rhs) :priority(rhs.priority) { logCall("PriorityCustomer copy constructor"); } PriorityCustomer & PriorityCustomer::operator=(const PriorityCustomer & rhs) { logCall("PriorityCustomer copy assignment operator"); priority = rhs.priority; return *this; }
base class name and lastTransaction Not copied
At any time, as long as we take responsibility for it"derived class compose copying function"The heavy responsibility of, It must also be copied with care base class Ingredients. Which ingredients are often private,therefore We can't access them directly. We should let them derived class of copying Function call corresponding bass class Function:
PriorityCustomer::PriorityCustomer(const PriorityCustomer & rhs) :Customer(rhs), //Call the copy constructor of base class priority(rhs.priority) { logCall("PriorityCustomer copy constructor"); } PriorityCustomer & PriorityCustomer::operator=(const PriorityCustomer & rhs) { logCall("PriorityCustomer copy assignment operator"); Customer::operator=(rhs); //Assign value to base class component priority = rhs.priority; return *this; }
please remember
- The Copying function should ensure that "all member variables in the object" and "all base class components" are copied.
- Do not try to implement another copying function with one copying function. The common function should be put into the third function and called by two copying functions