26:尽可能延后变量定义式的出现时间¶
- 原因一:程序前面部分可能有if判断、异常处理等,可能不会运行到后面部分
- 原因二:直接构造的效率高于默认构造+赋值
- 原因三:变量可能在循环中使用,变量定义在循环内部而非循环前面,可以避免将变量的作用域扩大;除非对循环部分的性能有要求。
27:尽量少做转型动作¶
- 三种风格的转型:
- C语言风格:
(T)expression
- 函数风格:
T(expression)
- C++风格:
const_cast<T>(expression)
:去除const属性dynamic_cast<T>(exprssion)
:将指向为基类的指针转型为指向派生类的指针,可能耗费重大运行成本reinterpret_cast<T>(expression)
static_cast<T>(expression)
:最常用
28:避免返回handles指向对象内部成分¶
- handles:引用、指针、迭代器
- 避免返回指向内部对象的handles,返回一个成员变量的副本
- 增加可封装性
- 帮助const成员函数的行为像一个const
- 将发生”dangling handles“的可能性降到最低(当临时对象析构后,也就无法通过handle获取对象内部的成员)
29:为“异常安全”而努力是值得的¶
- 异常安全的含义:当异常被抛出时
- 异常安全的函数提供三种不同级别的保证:
- 基本承诺:不发生数据败坏,但是不保证程序状态
- 强烈保证:程序状态不变(即程序回复到”调用函数之前“的状态)
- 通过RAII和调换代码顺序实现
- 或者通过copy and swap实现:创建副本资源并进行操作,所有操作完成后,使用一个不会抛出异常的swap将副本与当前资源进行交换
- 不抛掷承诺:总能完成功能,作用域内置类型上的所有操作都提供nothrow承诺
- 强烈保证有时无法实现
- 异常安全保证具有木桶效应
30:透彻了解内联的里里外外¶
inline
最初只是针对编译器的优化建议,而非强制;是否内联由优化等级所控制,与是否内联无关
- 声明:
- 隐式声明:将函数定义与类内部(但不是一种好的编程风格)
- 显示声明:
inline
- 内联函数通常被置于头文件中,因为内联大部分情况下时编译期行为
inline
必须放在函数定义前- 从实现上看,
inline
放在函数声明前不起作用 - 从编程风格看,应该严格区分声明与定义,而且用户不需要、也没有必要知道该函数是否内联
inline
只是对编译器的一个申请,不是强制命令
31:将文件间的编译依存关系降到最低¶
- pimpl idiom(pimpl:pointer to implementation)设计思想:
- 原来main class包含类的具体实现逻辑
- 现在将main class中具体实现逻辑,放到一个实现类Impl中,在private中添加一个指向Impl的指针
- 因此main class只是提供接口,实现类Impl负责实现接口,”类的接口与实现分离“
- 背景:即使只是改动类的实现,而不改变类的接口,这样所有包含该类的源码都要重新编译
- 根本原因在于,编译器在编译期必须知道对象的大小,如果不知道类的定义,就无法为对象分配内存
- 方法一:提供句柄类,用”声明的依存性“替换”定义的依存性“
- 原来:假设1000个文件依赖于Person.h,这1000个文件都要重新编译链接
1
2
3
4
5
6
7
8
| // Person.h
class Person{
public:
std::string name() const;
private:
std::string mName;
}
// 假设在Person.cpp中,略微修改了std::string Person::name()的实现,1000个文件需要全部重新编译
|
- 现在:只需要修改PersonImpl的具体实现,重新编译这一个文件即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| // Person.h
class PersonImpl; // PersonImpl声明
class Person{
public:
std::string name() const;
private:
PersonImpl *pImpl;
}
// Person.cpp
#include "Person.h"
#include "PersonImpl.h"
std::string Person::name(){
return pImpl->name(); // 调用实现类中同名函数
}
// PersonImpl.h
class PersonImpl{ // PersonImpl与Person有相同的public函数,且Person的private数据成员移动到了PersonImpl的private部分
public:
std::string name() const;
private:
std::string mName;
}
// PersonImpl.cpp
#include "PersonImpl.h"
std::string PersonImpl::name() {return mName;}
|
- 方法二:将句柄类定义为抽象类
- 基类中定义一个工厂方法,返回动态类型为派生类,静态类型为基类的指针
- 因此修改派生类中的方法的实现逻辑,不会影响到基类,”类的接口与实现分离“
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // Person.h
class Person{
public:
Person();
virtual std::string name() const;
static std::shared<Person> create(const std::string& name);
virtual ~Person();
};
// Person.cpp
#include "Person.h"
#include "RealPerson.h"
std::shared<Person> Person::create(const std::string& name){
return std::shared<Person>(new RealPerson(name))
}
// RealPerson.h
class RealPerson: public Person{
public:
RealPerson(std::string& name): mName(name) {};
virtual std::string name() const;
virtual ~RealPerson();
private:
std::string mName;
}
|
- 参考