C++核心指南:P.6 编译时无法做的检查应当在运行时做

C++核心指南:P.6 编译时无法做的检查应当在运行时做

P.6: 编译时无法做的检查应当在运行时做

原因

留下难以检测的错误在程序中会导致崩溃和糟糕的结果。

Note

理想情况下我们在编译时或运行时捕获所有的错误(非程序员的逻辑错误),然而并不可能在编译时捕获所有的错误,通常也不值得在运行捕获所有剩余的错误,然而我应当尽力编写在给定充足资源(分析程序,运行时检查,机器资源,时间等)的情况下原则上可以被检查的代码。

糟糕的例子

// 分开编译,可能会动态加载
extern void f(int* p);
void g(int n)
{
// 糟糕: 元素的数量没有传递给 f()
f(new int[n]);
}

在这里,元素的数量这一个关键信息被彻底“模糊”了,以至于静态分析可能变得不再可行,当“f()”是ABI的一部分时可能会让动态检查非常困难,以至于不能“检测”这个指针。我在免费存储中嵌入有用的信息,但这需要对系统进行全局更改,也许还需要对编译器进行更改,这样的设计使得错误检测非常困难。

糟糕的例子

当然,我们也可以将无数的数据随指针一起传进去:

// separately compiled, possibly dynamically loaded
extern void f2(int* p, int n);
void g2(int n)
{
f2(new int[n], m); // 糟糕:可能会将错误的无数数量传给f()
}

将元素的数量作为参数传递要比仅仅传递指针并依赖某种(未声明的)约定来了解或发现元素的数量更好(而且更常见)。然而(如所示),一个简单的拼写错误可能会导致严重的错误。f2()的两个参数之间的联系是习惯性的,而不是显式的。

同样,它也暗示着f2()支持delete它的参数(或者,这是调用者犯的第二个错误?)。

糟糕的例子

标准库的资源管理指针指向对象时也无法传递大小:

// 分开编译,可能动态加载
// NB: 假设这里的调用代码是 ABI-兼容的, 使用兼容的C++编译器以及相同的stdlib实现
extern void f3(unique_ptr, int n);

void g3(int n)
{
f3(make_unique(n), m); // 糟糕: 分开传递所有权和大小
}

Example

我们需要将指针和元素个数作为一个整体对象进行传递:

extern void f4(vector&); // 分开编译,可能动态加载
extern void f4(span); // 分开编译,可能动态加载
// NB: 假设这里的调用代码是 ABI-兼容的, 使用兼容的C++编译器以及相同的stdlib实现
void g3(int n)
{
vector v(n);
f4(v); // 传递一个引用,保留所有权(ownership)
f4(span{v}); // 传递一个视图(view),保留所有权
}

这种设计将元素的数量作为对象的一个组成部分,这样错误就不太可能发生,如果条件允许,动态(运行时)检查也是可行的。

Example

我们如何同时传递所有权和验证所需的所有信息?

vector f5(int n) // OK: move
{
vector v(n);
// ... initialize v ...
return v;
}
unique_ptr f6(int n) // 糟糕: 丢失了`n`
{
auto p = make_unique(n);
// ... initialize *p ...
return p;
}
owner f7(int n) // 糟糕: 丢失了`n`,并且可能会忘记`delete`
{
owner p = new int[n];
// ... initialize *p ...
return p;
}


分享到:


相關文章: