Implementations
Item 26: Postpone variable definitions as long aspossible.
1. Because you could not have usedthe defined variables, while defining a variable need to call its constructorand destroying a variable need to call its destructor. These all wastecomputation time.
2. Calling directly constructorswith real parameters will be more effective than calling their defaultconstructors and assignment constructors, such as;
void encrypt(std::string& s);
std::string encryptPassword(const std::string&password)
{
std::string encrypted;
encrypted= password;
encrypt(encrypted);
returnencrypted;
}
std::stringencryptPassword(const std::string&password)
{
std::string encrypted(password);
encrypt(encrypted);
returnencrypted;
}
3. Definitions in cycle are better,or not.
Widgetw;
for(int i=0;i<n;i++)
{
w = tempWidget;
}
for(int i=0;i<n;i++)
{
Widget w(tempWidget);
}
Method 1: 1 constructor + 1 constructor + nassignments
Method 2: n constructors + n destructors
If an assignment is more effective than aconstructor + a destructor, method1 will be selected, otherwise method2.Meanwhile, the “w” in method2 has smaller scope, which contributes tounderstand and maintain.
Item 27: Minimize casting
1. Four conversion functions:
const_cast<T>(), the only way to convert const intonon-const.
dynamic_cast<T>(), safe down-casting, based on classhierarchy.
reinterpret<T>(), execute lower-class conversion.
static_cast<T>(); force implicit conversion, such asnon-const to const, int to double, void* to type*, pointer-to-base topointer-to-derived.
2. New conversion advantages:
1) They are easy to be identified.
2) It convenient for compiler todetect mistakes. Throwing the const limitation can be only implemented byconst_cast<T>.
3. Function conversion T():
void doSomeWork(constWidget& w);
doSomeWork(Widget(15));
doSomeWork(static_cast<Widget>(15));
The“Widget(15)” is also a conversionaction, but we had better to use “static_cast<Widget>(15)” to replace it.
4. The conversion not only letcompiler operate objects according to appointed type, but also change theobjects physical memory, such as the “int” and “double” have different physicalmemory.
5. The conversion based on objectmemory layout should be prohibited, because the different compilers havedifferent object memory layout, so that conversion by that way have badportability.
6. The following code is processof “Derived*” to “Base*”, and in this case there is an offset being done on “Derived*”and then to get right value of “Base*”.
class Window
{
public:
Window():count(0){}
virtual void onResize(){count++;}
protected:
int count;
};
class SpecialWindow:publicWindow
{
public:
SpecialWindow(){}
virtual void onResize()
{
static_cast<Window>(*this).onResize();
count++;
}
int output(){return count;}
};
int main()
{
SpecialWindow swindow;
swindow.onResize();
std::cout<<swindow.output()<<std::endl;
return 0;
}
Note:where “static_cast<Window>” just convert temporary copy of “*this” into Window, so thepart that the copy changes is not saved in the base part in objects ofSpecialWindow. If the pointer is used, new mistakes will be brought.
virtual void onResize()
{
static_cast<Window*>(this)->onResize();
count++;
}
After the derived pointer is converted intobase pointer, the base pointer call the virtual function onResize, and thecalling would be forwarded to its derived class, so the program will die in theonResize() in derived class. The following code is right way to reach our goal.
virtual void onResize()
{
Window::onResize();
count++;
}
7. Use the base class pointers tocall derived class, there the following few ways:
1) Container including the derivedclass;
2) Virtual function, polymorphic;
Item 28: Avoid returning “handles” to object internals
1. In order to ensure encapsulation(封装性), we should avoid returning “handles” to object internals, wherethe “handles” include pointer, reference and iterator. Because if you do it,the access to private members will get the same as the function, in otherwords, the private members will be converted into public members.
class YPoint
{
public:
YPoint(int x,int y):m_x(x),m_y(y){}
YPoint():m_x(0),m_y(0){}
void setX(int newVal){m_x = newVal;}
void setY(int newVal){m_y = newVal;}
int getX(){return m_x;}
int getY(){return m_y;}
private:
int m_x,m_y;
};
struct YRectData
{
YRectData(constYPoint& point1, const YPoint& point2):
top_left(point1),bottom_right(point2)
{}
YPoint top_left;
YPoint bottom_right;
};
class YRectangle
{
public:
YRectangle(constYPoint& point1,const YPoint& point2):
m_pData(new YRectData(point1,point2))
{}
YPoint& upperLeft() const {return m_pData->top_left;}
YPoint& lowerRight() const {return m_pData->bottom_right;}
private:
std::tr1::shared_ptr<YRectData>m_pData;
};
int main()
{
YPoint x(0,0),y(1,1);
YRectangle rectangle(x,y);
rectangle.lowerRight().setX(3);
std::cout<<rectangle.lowerRight().getX()<<std::endl;
std::cout<<rectangle.lowerRight().getY()<<std::endl;
return 0;
}
Of course, we can add “const” limitation inthe front of functions.
constYPoint& upperLeft() const {return m_pData->top_left;}
constYPoint& lowerRight() const {return m_pData->bottom_right;}
However, there are still problems in thiscase. For example:
constYPoint* scope;
{
YPointx(0,0),y(1,1);
YRectanglerectangle(x,y);
scope= &rectangle.lowerRight();
std::cout<<scope->getX()<<std::endl<<scope->getY()<<std::endl;
}
std::cout<<scope->getX()<<std::endl<<scope->getY()<<std::endl;
The output is 1 and 1 in the scope of “rectangle”, but beyond the scope, its member variables are destructed, so theresults are not defined. All in all, we should avoid returning “handles” toobject internals.
Item 29: Strive for exception-safe code
1. Exception-safe function ensuresthe following three points:
1) Basic guarantee. If anexception is thrown, the program should be also in a valid state.
2) Strong guarantee. If anexception is thrown, the environment of program is not changed.
3) No exception. Ensure noexception to be thrown.
voidprettymenu::changeBackground(std::istream& imgSrc)
{
lock(&mutex);
delete bgImage;
++imageChanges;
bgImage= new Image(imgSrc);
unlock(&mutex);
}
voidprettymenu::changeBackground(std::istream& imgSrc)
{
Lockm1(&mutex);
Image*pold = bgImage;
bgImage= new Image(imgSrc);
++imageChanges;
delete pold;
}
class PrettyMenu
{
std::tr1::shared_ptr<Image>bgImage;
};
voidPrettyMenu::changeBackground(std::istream& imgSrc)
{
Lockm1(&mutex);
bgImage.reset(new Image(imgSrc));
++imageChanges;
}
Note: there are three versions above, thefirst cannot ensure program state stay constant when the “new Image(imgSrc)” throwsexceptions, while the second version can do basically this, but it is not thebetter. The third use the smart pointer to solve this problem. However, thisprogram still exit problems, which are the input stream problem (std::istream&imgSrc). If the constructor of Image throw exceptions,the read markers in the input stream have been removed, which will be a visibleprocess for other program, so there are better cases such as copy and swap.
2. Copy and swap.
struct PMImpl
{
std::tr1::shared_ptr<Image>bgImage;
int imageChange;
};
class PrettyMenu
{
public:
void changeBackground(std::istream&);
private:
Mutexmutex;
std::tr1::shared_ptr<PMImpl>pImpl;
};
voidPrettyMenu::changeBackground(std::istream& imgSrc)
{
using std::swap;
Lockm1(&mutex);
std::tr1::shared_ptr<PMImpl>pNew(new PMImpl(*pImpl));
pNew->bgImage.reset(new Image(imgSrc));
++pNew->imageChange;
swap(pImpl,pNew);
}
Use a “struct” save resources, and use ashared_ptr to pointer it in the class. If we want to update the resources,firstly build a copy for the old resources, then we operate the copy to ensurethe origin resources are not changed when the exceptions are thrown, which issimilar to atomic operation.
3. Strong guarantee can beachieved by “copy and swap”, but it may consume more memory and machine time.Meanwhile if the all parts of the function don’t provide at least strongguarantee, the whole function will be not able to provide strong guarantee, asa consequence, memory and time consumption will be of little value.
4. What we should stress is thatsafe guarantee function provides is restricted by the weakest safe guarantee.
Item 30: Understand the ins and outs of inlining.
1. The inline functions generate originfunction at the compile phase.
2. If an inline function is toocomplex, a virtual function, or a pointer function, the compiler will not agreethe application.
3. The virtual function isappointed dynamically during the running, if it is also an inline function, thecompiler will not know which function is replaced.
4. If the pointer function is alsoan inline function, it is not inlined equally.
5. If you want to add inline forconstructors, you should consider more carefully.
classBase
{
public:
Base(){}
private:
std::stringbm1,bm2;
};
class Derived:public Base
{
public:
Derived(){}
private:
std::stringdm1,dm2,dm3;
};
Derived::Derived()
{
Base::Base();
try {dm1.std::string::string();}
catch(...)
{Base::~Base();
throw;
}
try{dm2.std::string::string();}
catch(...)
{
dm1.std::string::~string();
Base::~Base();
throw;
}
try{dm3.std::string::string();}
catch(...)
{
dm2.std::string::~string();
dm1.std::string::~string();
Base::~Base();
throw;
}
}
Note: the Derived(){} seems empty, but thecompiler will provide those codes for it, of course, the fact may be not likethis, however it can only be more complex.
Item 31: Minimize compilation dependencies between files.
1. Firstly, what we need to knowis that if a header file is modified, all files including the file and it willbe built again, even if these files including it are not modified. So we havetwo ways to improve the problem, and they are Handle classes and Interfaceclasses.
2. The Handle classes: we use aheader file to save the declarations and there are only declarations in thefile. Then we use a pointer, which had better to be a smart pointer, to pointtheir implementations. In this way, the modifications in implementation fileswill not influence other files. Lastly, separating interface and implementationcomes true.
// version 1, handles
#include <string>
#include <memory>
//declare these classes to passcompiler
class Date;
class Address;
class PersonImpl;
class Person
{
public:
Person(const std::string& name, constDate& birthday,
const Address& addr);
std::stringname() const;
std::stringbirthday() const;
std::stringaddress() const;
private:
std::tr1::shared_ptr<PersonImpl>m_pImpl;
};
#include "Person.h"
#include "personimpl.h"
Person::Person(conststd::string& name, const Date&birthday,
const Address& addr):m_pImpl(new PersonImpl(name,birthday,addr))
{}
std::string Person::name() const
{
return m_pImpl->name();
}
std::string Person::birthday() const
{
return m_pImpl->birthday();
}
std::string Person::address() const
{
return m_pImpl->address();
}
#include "Address.h"
#include "Date.h"
#include <string>
class PersonImpl
{
public:
PersonImpl(const std::string& name,constDate& birthday,
const Address& addr);
std::stringname()const;
std::stringbirthday() const;
std::stringaddress() const;
private:
std::stringtheName;
DatetheBirthday;
AddresstheAddress;
};
PersonImpl::PersonImpl(conststd::string& name,const Date& birthday,
const Address& addr):theName(name),theBirthday(birthday),theAddress(addr)
{
}
std::string PersonImpl::name() const
{
return this->theName;
}
std::string PersonImpl::address() const
{
return this->theAddress.getAddress();
}
std::string PersonImpl::birthday() const
{
return this->theBirthday.getDate();
}
Note: the string is not a class, which isobtained by “typedef basic_string<char>”, so it can’t be declared in thefront of program.
3. The principles in Handleclasses:
1) Can use object references andobject pointers, not use objects.
2) Try to replace class definitionwith class declaration.
4. Interface classes. It makes “Person”become a special abstract base class. It descripts each interface of itsderived classes, and doesn’t have member variables and constructors, and justhave virtual destructor and some pure virtual functions.
#include<memory>
#include<string>
//version 2
class Address;
class Date;
class Person2
{
public:
virtual ~Person2()=0;
virtual std::string name() const=0;
virtual std::string birthday() const =0;
virtual std::string address() const =0;
static std::tr1::shared_ptr<Person2> create(const std::string& name,constDate& birthday,
const Address& addr);
};
#include "person2.h"
#include "Address.h"
#include "date.h"
class RealPerson:public Person2
{
public:
virtual ~RealPerson();
RealPerson(const std::string& name, constDate& birthday,
const Address& addr);
virtual std::string name() const;
virtual std::string birthday() const;
virtual std::string address() const;
private:
std::stringtheName;
DatetheBirthday;
AddresstheAddress;
};
#include "realperson.h"
RealPerson::~RealPerson(){}
RealPerson::RealPerson(conststd::string& name, const Date&birthday,
const Address&addr):theName(name),theBirthday(birthday),theAddress(addr){}
std::string RealPerson::name() const
{
return theName;
}
std::string RealPerson::birthday() const
{
return theBirthday.getDate();
}
std::string RealPerson::address() const
{
return theAddress.getAddress();
}
//the create function of base class
Person2::~Person2(){}
std::tr1::shared_ptr<Person2>Person2::create(const std::string& name,const Date& birthday,
const Address& addr)
{
return std::tr1::shared_ptr<Person2>(new RealPerson(name,birthday,addr));
}
Note:
1) Ifyou declare the destructor as a pure virtual function, you have to provide adefinition for the destructor, because it will be called after the destructorsof its derived classes are called. So the process needs a real destructorfunction.
2) Ifthe implementations are modified, only implementation files will be built, andthe user files will not built. However if the interfaces are modified, theirimplementation files and user files will be all built again.
5. Although these ways can makecompilation dependencies between files smaller, they can need more memory andmachine time due to more classes and polymorphic(多态, e.g.vptr virtual table pointer)
6. For a forward declaration, it can be used when thetype is used as pointer or reference, but if you want declare an object of thetype in the file, you must get its completive declaration, because compilerneeds to know the size of the object.