实现
条款26: 尽可能延后变量定义式的出现时间
“尽可能延后”的真正意义:
1)不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至 2)应尝试延后定义直到能提供初值实参为止
第二点的原因:通过默认构造函数构造出一个对象然后对它赋值”比“直接在构造函数时制定初值”效率差
//方法1,1个构造函数+1个析构函数+n个赋值操作;
Widget w;
for (int i = 0; i < n; ++i)
{
w = some value dependent on i;
...
}
//方法2,n个构造函数+n个析构函数
for (int i = 0; i < n; ++i)
{
Widget w(some value dependent on i);
...
}
若方法2的开销小于方法1或对性能要求不高时选择方法2 ,因为方法2满足条款1
条款27: 尽量少做转型动作
static_cast: 用来强迫隐式转换,例如将non-const转型为const,int转型为double等等。
const_cast: 通常被用来将对象的常量性转除;即去掉const。
dynamic_cast: 主要用来执行“安全向下转型”,也就是用来决定某对象是否归属继承体系中的某个类型。
reinterpret_cast: 意图执行低级转型,实际动作可能取决于编译器,这也就表示它不可移植。
class Window{
public:
virtual void resize(int s){ size = 0;}
private:
int size;
};
class SpecialWindow : public Window{
public:
virtual void resize(int s){
static_cast<Window>(*this).resize(s);
}
};
注意 static_cast<Window>(*this).resize(s):
在当前对象的副本上调用resize,所以原对象根本没有变化!!!
正确的方式:
class SpecialWindow : public Window{
public:
virtual void resize(int s){
Window::resize(s);
}
};
使用dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行 derived class函数操作,但你只有一个base指针或引用。
可以使用1)虚函数或 2) 直接修改函数接口,避免使用base指针或引用
条款28: 避免返回handles指向对象内部成员
问题: 破坏了封装性,可能会导致悬空指针或引用或迭代器
条款29: 为“异常安全”而努力是值得的
当异常被抛出时,带有异常安全的函数:
1. 不泄露任何资源 (leakage)
2. 不允许数据败坏 (integrity)
Problem
class PrettyMenu {
public:
...
void changeBackground(std::istream& imgSrc); // change background image
...
private:
Mutex mutex; // mutex for this object
Image *bgImage; // current background image
int imageChanges; // # of times image has been changed
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
lock(&mutex); // acquire mutex (as in Item 14)
delete bgImage; // get rid of old background
++imageChanges; // update image change count
bgImage = new Image(imgSrc); // install new background
unlock(&mutex); // release mutex
}
如上代码没有满足异常安全的两点要求:
1) bgImage = new Image(imgSrc)异常返回时,lock没有释放,可能会导致死锁。
2) bgImage = new Image(imgSrc)异常返回时, imageChanges已经增加,但实际并没有变化,因为没有new成功,破坏integrity
解决第一点:利用RAII管理资源
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock m(&mutex); // Lock 利用shared_ptr管理锁
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
}
解决第二点:利用copy and swap
先创建一个修改好的对象,成功后,再和待修改对象交换, 确保对象修改的完整性(修改完或不修改)
struct PMImpl { // item31
std::tr1::shared_ptr<Image> bgImage;
int imageChanges;
};
class PrettyMenu {
...
private:
Mutex mutex;
std::tr1::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
using std::swap;
Lock ml(&mutex);
std::tr1::shared_ptr<PMImpl>
pNew(new PMImpl(*pImpl)); // copy
pNew->bgImage.reset(new Image(imgSrc));
++pNew->imageChanges;
swap(pImpl, pNew); // swap
}
异常安全函数提供三个保证之一:
1. 函数提供基本保证
2. 函数提供强力保证
3. 函数提供不抛出保证: int doSomething() throw();
函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者
条款30: 透彻了解inlining的里里外外
inline的使用
1) inline不要在函数声明时使用,在函数定义时使用 (用户无须知道一个函数是不是inline的)
2) 将函数定义于class内时,相当于在函数前隐式的加了inline
3) inline 具有内部链接的性质,即当inline函数A声明在B.h,实现在C.cpp时,模块D调用A函数时,编译D没问题,但链接时将出现错误
4) 使用inline只是程序员的一种建议,编译器根据实际需要,判断是否将函数展开
inline优势:
编译期间利用宏的方式,将函数调用展开,减少函数调用开销。
inline劣势:
1)过度使用inline导致代码膨胀,继而可能导致cache命中率降低,影响性能
2)导致模块之间的耦合加剧,因为inline函数的修改一定导致调用该函数的客户代码重新编译。non-inline函数可能只需要重新链接就行了
3)大部分调试器不能针对inline函数进行调试
建议:
0)开始时先不要将任何函数声明为inline,或至少将inlining施行范围局限在那些“一定成为inline”或“十分平淡无奇”的函数身上
1) 将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化
2) 不要只因为function templates出现在头文件,就将它们声明为inline
条款31:将文件间的编译依存关系降至最低
问题
对一个头文件中的class类中的成员(即便是private成员)进行修改,导致其他调用该头文件的模块重新编译(编译依赖)
// #include "Person.h" 头文件
#include <string>
#include "Date.h"
#include "Address.h"
class Person {
public:
Person(const std::string& name, const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private:
std::string theName; // implementation detail
Date theBirthDate; // implementation detail
Address theAddress; // implementation detail
};
对头文件“Date.h”或"Address.h的任意修改都会导致调用Person.h的客户程序的重新编译
Solution 1 : 使用 pimpl idiom机制
头文件定义一个pimpl的指针,将Person部分成员和函数的实现交由 pImpl指向的类实现
// #include "Person.h" 头文件
#include <string>
class PersonImpl; // 前置声明
class Date; // 前置声明
class Address; // 前置声明
class Person {
public:
Person(const std::string& name, const Date& birthday, const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private: // ptr to implementation;
std::tr1::shared_ptr<PersonImpl> pImpl;·--// see Item 13 for info on
};
// #include "PersonImpl.h" 头文件
#include <string>
#include "Date.h"
#include "Address.h"
struct PersonImpl{
std::string theName; // implementation detail
Date theBirthDate; // implementation detail
Address theAddress; // implementation detail
std::string name() const { return theName; }
std::string birthDate() const { return theBirthDate.birthDate(); }
std::string address() const { return theAddress.address(); }
};
// #include "Person.cpp"
#include "Person.h"
#include "PersonImp.h"
std::string name() const{
return pImpl->name();
}
std::string birthDate() const{
return pImpl->birthDate();
}
std::string address() const {
return pImpl->address();
}
Solution 2 利用抽象基类
// #include "Person.h" 头文件 抽象基类
#include <string>
#include <memory>
class Person{
public:
virtual ~Person(){} // 书本这个地方出错
virtual std::string getName() const = 0;
virtual unsigned int getAge() const = 0;
virtual char getSex() const = 0 ;
// 这个static函数是关键
static boost::shared_ptr<Person> create(const std::string& name, unsigned int age, char sex);
};
//#include "RealPerson.cpp" 对抽象基类的实现
#include "Person.h"
#include <memory>
class RealPerson : public Person
{
public:
RealPerson(const std::string& _name, unsigned int _age, char _sex): name(_name), age(_age),sex(_sex) {}
~RealPerson() {}
std::string getName() const { return name; }
unsigned int getAge() const { return age; }
char getSex() const { return sex; }
private:
std::string name;
unsigned int age;
char sex;
};
boost::shared_ptr<Person> Person::create(const std::string& _name, unsigned int _age, char _sex)
{
return boost::shared_ptr<Person>(new RealPerson(_name, _age, _sex));
}
// 客户代码
#include "Person.h"
int main(){
boost::shared_ptr<Person> person = Person::create("ShiYang",27, 'F');
...
return 0;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。