CppCoreGuidelines是一篇比较好的cpp学习指南。本想认真翻译下,但在github上发现已经有了中文翻译。先摘取比较重要的章节学习下。
程序员应当熟悉:
但有些场景无法在编译器确定静态安全类型:
示例中,尽量保障交互是变量生命周期的有效性及安全。
extern void f4(vector&); // 分离编译,可能会被动态加载
extern void f4(span); // 分离编译,可能会被动态加载
// NB: 这里假定调用代码是 ABI 兼容的,使用的是
// 兼容的 C++ 编译器和同一个 stdlib 实现
void g3(int n)
{
vector v(n);
f4(v); // 传递引用,保留所有权
f4(span{v}); // 传递视图,保留所有权
}
基于RAII
的方式而不用基于fopen
的方式避免内存泄漏。具体可参考资源管理。
void f(char* name)
{
ifstream input {name};
// ...
if (something) return; // OK: 没有泄漏
// ...
}
// BAD
void f(char* name)
{
FILE* input = fopen(name, "r");
// ...
if (something) return; // 不好的:如果 something == true 的话,将会泄漏一个文件句柄
// ...
fclose(input);
}
// BAD:每次都计算strlen
void lower(zstring s)
{
for (int i = 0; i < strlen(s); ++i) s[i] = tolower(s[i]);
}
具体参考:Con: 常量和不可变性
template
requires input_iterator && equality_comparable_with, Val>
Iter find(Iter first, Iter last, Val v)
{
// ...
}
参见: 泛型编程和概念。
void copy_n(const T* p, T* q, int n); // 从 [p:p+n) 复制到 [q:q+n)
->
void copy(span r, span r2); // 将 r 复制给 r2
复杂的初始化可能导致未定义的执行顺序。
template
OutputIterator merge(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2,
OutputIterator result, Compare comp);
->
template
OutputIterator merge(InputRange1 r1, InputRange2 r2, OutputIterator result);
空的(没有非静态成员数据)抽象类要比带有状态的基类更倾向于保持稳定。
class Shape { // 不好: 接口类中加载了数据
public:
Point center() const { return c; }
virtual void draw() const;
virtual void rotate(int);
// ...
private:
Point c;
vector outline;
Color col;
};
class Shape { // 有改进: Shape 是一个纯接口
public:
virtual Point center() const = 0; // 纯虚函数
virtual void draw() const = 0;
virtual void rotate(int) = 0;
// ...
// ... 没有数据成员 ...
// ...
virtual ~Shape() = default;
};
不同的编译器会实现不同的类的二进制布局,异常处理,函数名字,以及其他的实现细节。
由于私有数据成员参与类的内存布局,而私有成员函数参与重载决议, 对这些实现细节的改动都要求使用了这类的所有用户全部重新编译。而持有指向实现的指针(Pimpl)的 非多态的接口类,则可以将类的用户从其实现的改变隔离开来,其代价是一层间接。
需要用 constexpr 来告诉编译器允许对其进行编译期求值。
constexpr int min(int x, int y) { return x < y ? x : y; }
void test(int v)
{
int m1 = min(-1, 2); // 可能进行编译期求值
constexpr int m2 = min(-1, 2); // 编译期求值
int m3 = min(-1, v); // 运行期求值
constexpr int m4 = min(-1, v); // 错误: 无法在编译期求值
}
如果不打算抛出异常的话,程序就会认为无法处理这种错误,并且应当尽早终止。把函数声明为 noexcept 对优化器有好处,因为其减少了可能的执行路径的数量。它也能使发生故障之后的退出动作有所加速。
TOC