条款26.尽可能延后变量定义式的出现时间
- 你不止应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后定义直到能够给它初始实参为止。
- 尽可能延后变量定义式的出现,这样做可增加程序的清晰度并改善程序效率。
条款27.尽量少做转型动作
C++新类型的转型:
- dynamic_cast运算符的主要用途:
将基类的指针或引用安全地转换成派生类的指针或引用,并用派生类的指针或引用调用非虚函数。如果是基类指针或引用调用的是虚函数无需转换就能在运行时调用派生类的虚函数。
前提条件:当我们将dynamic_cast用于某种类型的指针或引用时,只有该类型含有虚函数时,才能进行这种转换。否则,编译器会报错。
dynamic_cast运算符的调用形式如下所示:
dynamic_cast<type*>(e) //e是指针
dynamic_cast<type&>(e) //e是左值
dynamic_cast<type&&>(e)//e是右值
e能成功转换为type*类型的情况有三种:
1).e的类型是目标type的公有派生类:派生类向基类转换一定会成功。
2).e的类型是目标type的基类,当e是指针指向派生类对象,或者基类引用引用派生类对象时,类型转换才会成功,当e指向基类对象,试图转换为派生类对象时,转换失败。
3).e的类型就是type的类型时,一定会转换成功。
- const_cast:将对象的常量性转除。
- static_cast:强迫隐式转换。
eg1:
class CastWindow {
public:
explicit CastWindow(int size) :m_size(size) {}
virtual void onResize() {
m_size += 50;
cout << "CastWindow size:" << m_size << endl;
}
protected:
int m_size;
};
class DerivedCastWindow:public CastWindow {
public:
DerivedCastWindow(int size) :CastWindow(size) {}
virtual void onResize() {
//static_cast<CastWindow>(*this).onResize();//此处调用的转型操作所建立的基类副本,而不是真正的基类成分
CastWindow::onResize();//此处调用的才是真正的基类成分
m_size += 100;
cout << "DerivedCastWindow size:" << m_size << endl;
}
};
void test54() {
DerivedCastWindow dcw(100);
dcw.onResize();
}
总结:
1.如果可以,尽量避免转型,特别是在注重效率的代码中避免danamic_cast, 如果有个设计需要转型动作,试着发展无需转型的替代设计.
2. 如果转型是必要的,试着将它隐藏于某个函数背后.客户随后可以调用该函数,而不需将转型放进他们自己的代码内.
3. 宁可使用C++ style(新式)转型,不要使用旧式转型.前者很容易辨识出来,而且也比较有着分类的执掌.
条款28.避免返回handles指向对象内部成分
1.handles指对象内部成分的引用、指针和迭代器。
2.返回handles可能导致悬空指针的现象,如该handles指向一个临时对象。
3.避免返回handles(reference、指针、迭代器)指向对象内部。遵守这个条款可以增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handles,即悬空指针)的风险降到最低。
eg1:
class Point {
public:
explicit Point(int x, int y) :m_x(x), m_y(y) {}
void show() {
cout << "m_x:" << m_x << ", m_y:" << m_y << endl;
}
void set_x(int x) {
m_x = x;
}
private:
int m_x, m_y;
};
struct RectData {
Point uppLeft_handCorner;
Point lowRight_handCorner;
};
class Rectangle {
public:
Rectangle(RectData* pRd) :m_pData(pRd) {}
Point& UpperLeft() const {
return m_pData->uppLeft_handCorner;
}
Point& LowerRight()const {
return m_pData->lowRight_handCorner;
}
private:
std::tr1::shared_ptr<RectData> m_pData;
};
void test55() {
RectData rd1{ Point (10,15),Point(20,25)};
Rectangle rt1(&rd1);
//即使UpperLeft后面添加了const修饰,即UpperLeft无法修改成员变量的值,但用户依旧可以通过set_x()修改
//解决方法是在UpperLeft()的前面添加const修饰
rt1.UpperLeft().set_x(30);
rt1.UpperLeft().show();
}
条款29.为“异常安全”而努力是值得的
1.异常安全函数提供以下是三个保证之一:
1). 基本保证:如果异常被抛出,程序内的任何事物保持在有效状态下.没有任何对象或数据结果会因此而败坏,所有对象都处于一种内部前后一致的状态.
2).强烈保证:如果异常被抛出,程序状态不改变.调用这样的函数需要有这样的认识:如果函数成功,就是完全成功,如果函数失败,程序会回复到"调用函数之前"的状态.
3).不抛出(nothrow)保证:承诺不抛出异常,因为它们总是能够完成它们原先承诺的功能.作用于内置类型身上的所有操作都提供nothrow保证.这是异常安全码中一个必不可少的关键基础材料.
2. "强烈保证"往往能够以copy and swap实现出来,但"强烈保证"并非对所有函数都可实现或具备现实意义.
3. 函数提供的"异常安全保证"通常最高只等于其所调用的函数中的"异常安全保证"的最弱者.
例1:
class Image {
public:
explicit Image(string color) :m_color(color) {}
void show() {
cout << m_color << endl;
}
private:
string m_color;
};
//以下的PrettyMenu::changeBackground会导致资源泄露和数据败坏:
class PrettyMenu {
public:
PrettyMenu(mutex* mu) :m_mut(mu) {}
void changeBackground(string color) {
(*m_mut).lock();
if (m_bgImg != NULL)
{
delete m_bgImg;
}
++m_imgChanges;
m_bgImg = new Image(color);//1.若new Image(color)失败抛出异常则m_mut不会被解锁;2.new失败抛出异常m_imgChanges却已经加1了
cout << "change to " << color << endl;
(*m_mut).unlock();
}
private:
mutex* m_mut;
Image* m_bgImg;
int m_imgChanges;
};
void thredFun(PrettyMenu& pm, vector<string>& v) {
for (auto& color : v) {
pm.changeBackground(color);
}
}
void test56() {
mutex mu;
vector<string> v1{"red1", "red2", "red3", "red4", "red5"};
vector<string> v2{"green1", "green2", "green3", "green4", "green5"};
PrettyMenu pm(&mu);
thread th1(thredFun,ref(pm), ref(v1));
thread th2(thredFun,ref(pm), ref(v2));
th1.join();
th2.join();
}
改进:
class Image {
public:
explicit Image(string color) :m_color(color) {}
void show() {
cout << m_color << endl;
}
private:
string m_color;
};
void myUnlock(mutex* pM) {
pM->unlock();
}
class ImgLock {
public:
explicit ImgLock(mutex* pMu) :m_muSptr(pMu, myUnlock) {
m_muSptr->lock();
}
private:
std::tr1::shared_ptr<mutex> m_muSptr;
};
class PrettyMenu {
public:
PrettyMenu(mutex* mu) :m_mut(mu) {}
void changeBackground(string color) {
int aa = 0;
{
ImgLock imglock1(m_mut);//由对象管理mutex
m_sptrBgImg.reset(new Image(color));//m_sptrBgImg只有在new Image(color) 成功后才会delete旧对象,并设置新对象
++m_imgChanges;
cout << "change to " << color << endl;
}
int bb = 0;
}
private:
mutex* m_mut;
//Image* m_bgImg;
std::tr1::shared_ptr<Image> m_sptrBgImg;//采用shared_ptr智能指针
int m_imgChanges;
};
void thredFun(PrettyMenu& pm, vector<string>& v) {
for (auto& color : v) {
pm.changeBackground(color);
}
}
void test56() {
mutex mu;
vector<string> v1{"red1", "red2", "red3", "red4", "red5"};
vector<string> v2{"green1", "green2", "green3", "green4", "green5"};
PrettyMenu pm(&mu);
thread th1(thredFun,ref(pm), ref(v1));
thread th2(thredFun,ref(pm), ref(v2));
th1.join();
th2.join();
}
条款30.透彻了解inlining的里里外外
1.一开始先不要将任何函数声明为inline,或至少将inlining施行范围局限在那些”一定成为inline”或“十分平淡无奇”的函数身上。
2.慎重使用inline便是对日后使用调试器带来帮助,不过这么一来也等于把自己推向手工最优化的道路。
3. 不要忘记 80-20 经验法则:平均一个程序往往将80%的执行时间花在20%的代码上头。所以,找出可以有效增进程序整体效率的20%代码,然后将它inline或接近所能地将它瘦身。
4.构造函数和析构函数往往是inlining的糟糕候选人,编译器会增加许多代码在里面。
5.隐式声明inline函数:将成员函数和友元函数的定义放在类定义里。
条款31.将文件间的编译依存关系降至最低