原因
因为这是C++!
注意
为了实现目标(例如开发速度,资源安全或者简化测试)花费的时间和空间不算浪费。“追求效率的另一个好处是,这个过程会迫使你更深入地理解问题。” -Alex Stepanov(PS:STL之父)
反例
struct X {
char ch;
int i;
string s;
char ch2;
X& operator=(const X& a);
X(const X&);
};
X waste(const char* p)
{
if (!p) throw Nullptr_error{};
int n = strlen(p);
auto buf = new char[n];
if (!buf) throw Allocation_error{};
for (int i = 0; i < n; ++i) buf[i] = p[i];
// ... manipulate buffer ...
X x;
x.ch = 'a';
x.s = string(n); // give x.s space for *p
for (gsl::index i = 0; i < x.s.size(); ++i) x.s[i] = buf[i]; // copy buf into x.s
delete[] buf;
return x;
}
void driver()
{
X x = waste("Typical argument");
// ...
}
这是在讽刺,但是这些甚至更糟糕的错误我们都在工程代码中看到过。注意X
的设计至少浪费了6字节(甚至更多)的空间。复制操作的虚假定义禁止了移动语义所以返回操作很慢(请注意这里不保证返回值优化RVO)。对buf
的new
和delete
是多余的;如果我们确实需要一个局部的字符串,应该用string
。还有几个性能bug和不必要的并发问题。
反例
void lower(zstring s)
{
for (int i = 0; i < strlen(s); ++i) s[i] = tolower(s[i]);
}
这是来自生产代码中的实例。我们可以看到在我们的条件下有i < strlen(s)
。这个表达式将在循环的每次迭代中进行评估,这意味着strlen
必须在每次循环时遍历字符串来计算长度。当字符串内容变化时,假定toLower
不会影响字符串长度,所以最好在循环外缓存长度避免每次迭代时的耗时。
注意
浪费的个例通常不重要,而且重要的地方通常可以被专家迅速消除。然而,在代码库中时空消耗的随意传播很容易变得影响重大,而且专家并不总是像我们想得那样有空。该规则(以及支持它的更具体的规则)的目的是在使用时将大多数C++相关的浪费消除在发生问题前。之后,我们可以查找与算法和需求相关的浪费,但这超出了这些准则的范围。
补充
许多更具体的规则旨在实现简化和消除非必要浪费的总体目标。
- 注意来自用户定义的非默认后缀 operator++ 或 operator-- 函数的未使用返回值。优先使用前缀形式。 (注意:“用户定义的非默认”旨在限制范围。如果在实践中仍然实例很多,请回顾此条。)