C++核心准则C.83:对于值类类型,考虑提供一个不会抛出异常的交换函数!
C.83: For value-like types, consider providing a noexcept swap function
C.83:对于值类类型,考虑提供一个不会抛出异常的交换函数
Reason(原因)
A swap can be handy for implementing a number of idioms, from smoothly moving objects around to implementing assignment easily to providing a guaranteed commit function that enables strongly error-safe calling code. Consider using swap to implement copy assignment in terms of copy construction. See also destructors, deallocation, and swap must never fail.
swap 功能可以在实现很多常规操作时提供便利。从顺畅地移动对象到更容易地实现赋值,以至提供有保证的提交函数,这个函数可以为不会失败的调用代码提供强有力的支持。
Example, good(示例)
class Foo {
public:
void swap(Foo& rhs) noexcept
{
m1.swap(rhs.m1);
std::swap(m2, rhs.m2);
}
private:
Bar m1;
int m2;
};
Providing a nonmember swap function in the same namespace as your type for callers' convenience.
为了调用者的方便,在和目标类型同一个命名空间中提供一个非成员的swap函数。
void swap(Foo& a, Foo& b)
{
a.swap(b);
}
Enforcement(实施建议)
(Simple) A class without virtual functions should have a swap member function declared.
(简单)不包含虚函数的类就应该定义一个swap函数。
(Simple) When a class has a swap member function, it should be declared noexcept.
(简单)如果一个类包含一个swap成员函数,这个函数应该被声明为noexcept。
C++核心准则C.84:swap函数不应该失败
C.84: A swap function may not fail
C.84:swap函数不应该失败
Reason(原因)
swap is widely used in ways that are assumed never to fail and programs cannot easily be written to work correctly in the presence of a failing swap. The standard-library containers and algorithms will not work correctly if a swap of an element type fails.
swap函数被广泛地使用的方式就是假设它永远不会失败,而且也很难写出即使swap出错也能正常动作的程序。标准库容器和算法在元素交换失败时也无法正常工作。
Example, bad(反面示例)
void swap(My_vector& x, My_vector& y)
{
auto tmp = x; // copy elements
x = y;
y = tmp;
}
This is not just slow, but if a memory allocation occurs for the elements in tmp, this swap may throw and would make STL algorithms fail if used with them.
这段代码的问题不仅是慢,而且如果因为tmp的元素发生了内存申请,如果使用它的话,这个swap可能抛出异常并令STL算法失败。
Enforcement(实施建议)
(Simple) When a class has a swap member function, it should be declared noexcept.
(简单)如果类包含swap成员函数,它应该被声明为noexcept!
C++核心准则C.86:保证==语义遵守操作数规则并不会抛出异常
C.86: Make == symmetric with respect to operand types and noexcept
C.86:保证==语义遵守操作数规则并不会抛出异常
Reason(原因)
Asymmetric treatment of operands is surprising and a source of errors where conversions are possible. == is a fundamental operations and programmers should be able to use it without fear of failure.
操作数的非对称处理会令人诧异,而且当可能发生类型转换时会成为错误的源头。==是一个基础的操作而且程序员应该可以使用它而不必担心失败。
Example(示例)
struct X {
string name;
int number;
};
bool operator==(const X& a, const X& b) noexcept {
return a.name == b.name && a.number == b.number;
}
Example, bad(反面示例)~ 成员函数比较运算符可以接受第二个操作数的类型转换,但无法接受第一个参数的类型转换 ~
class B {
string name;
int number;
bool operator==(const B& a) const {
return name == a.name && number == a.number;
}
// ...
};
B's comparison accepts conversions for its second operand, but not its first.
~※ B的比较运算符可以接受第二个操作数的类型转换,但无法接受第一个参数的类型转换!
Note(注意)
If a class has a failure state, like double's NaN, there is a temptation to make a comparison against the failure state throw. The alternative is to make two failure states compare equal and any valid state compare false against the failure state.
如果一个类有失败状态,就像双精度数的NaN,就会产生一种诱惑去比较失败状态抛出的异常。另外一种选择是将两个失败状态的比较结果视为相等,有效状态和无效状态的比较结果视为不相等。
Note(注意)
This rule applies to all the usual comparison operators: !=, <, <=, >, and >=.
这条规则同样被适用于通常的比较运算符:!=, <, <=, >, 和 >=.
Enforcement(实施建议)
Flag an operator==() for which the argument types differ; same for other comparison operators: !=, <, <=, >, and >=.
如果相等运算符的参数是其他类型,进行提示。其他的比较运算符也一样:!=, <, <=, >, and >=。
Flag member operator==()s; same for other comparison operators: !=, <, <=, >, and >=.
标记成员函数比较运算符,其他的比较运算符也一样:!=, <, <=, >, and >=。
C++核心准则C.87:小心基类的相等运算符
C.87: Beware of == on base classes
C.87:小心基类的相等运算符
Reason(原因)
It is really hard to write a foolproof and useful == for a hierarchy.
为继承体系写出简单又好用的相等运算符真的很难。
Example, bad(反面示例)
class B {
string name;
int number;
virtual bool operator==(const B& a) const
{
return name == a.name && number == a.number;
}
// ...
};
B's comparison accepts conversions for its second operand, but not its first.
B的相等比较运算符的第二个操作数接受类型转换,但是第一个不行。
class D :B {
char character;
virtual bool operator==(const D& a) const
{
return name == a.name && number == a.number && character == a.character;
}
// ...
};
B b = ...
D d = ...
b == d; // compares name and number, ignores d's character
d == b; // error: no == defined
D d2;
d == d2; // compares name, number, and character
B& b2 = d2;
b2 == d; // compares name and number, ignores d2's and d's character
Of course there are ways of making == work in a hierarchy, but the naive approaches do not scale
当然有办法让相等比较运算符在继承体系中动作,但是简单的方法不行。
Note(注意)
This rule applies to all the usual comparison operators: !=, <, <=, >, and >=.
本规则适用于所有的常见比较运算符:!=, <, <=, >, 和 >=。
Enforcement(实施建议)
Flag a virtual operator==(); same for other comparison operators: !=, <, <=, >, and >=.
提示被定义为虚函数的相等比较运算符;其他比较运算符也一样:!=, <, <=, >, 和 >=。
C++核心准则C.89:保证哈希不会抛出异常
C.89: Make a hash noexcept
C.89:保证哈希不会抛出异常
Reason(原因)
Users of hashed containers use hash indirectly and don't expect simple access to throw. It's a standard-library
requirement.
哈希容器的用户间接地使用哈希功能,不希望简单的操作发生异常。这是标准库的要求!
Example, bad(反面示例)
template<>
struct hash<My_type> { // thoroughly bad hash specialization 糟糕的hash特化
using result_type = size_t;
using argument_type = My_type;
size_t operator() (const My_type & x) const
{
size_t xs = x.s.size();
if (xs < 4) throw Bad_My_type{}; // "Nobody expects the Spanish inquisition!"
return hash<size_t>()(x.s.size()) ^ trim(x.s);
}
};
int main()
{
unordered_map<My_type, int> m;
My_type mt{ "asdfg" };
m[mt] = 7;
cout << m[My_type{ "asdfg" }] << '\n';
}
If you have to define a hash specialization, try simply to let it combine standard-library hash specializations with ^ (xor). That tends to work better than "cleverness" for non-specialists.
如果你已经定义了哈希特化,争取简单地实现为通过异或和标准库哈希特化的组合。
Enforcement(实现建议)
Flag throwing hashes.
提示抛出异常的哈希。
C++核心准则C.90:依靠构造函数和赋值运算符,而不是内存初始化和内存拷贝
C.90: Rely on constructors and assignment operators, not memset and memcpy
C.90:依靠构造函数和赋值运算符,而不是内存初始化和内存拷贝
Reason(原因)
The standard C++ mechanism to construct an instance of a type is to call its constructor. As specified in guideline C.41: a constructor should create a fully initialized object. No additional initialization, such as by memcpy, should be required. A type will provide a copy constructor and/or copy assignment operator to appropriately make a copy of the class, preserving the type's invariants. Using memcpy to copy a non-trivially copyable type has undefined behavior. Frequently this results in slicing, or data corruption.
标准C++机制通过调用构造函数构造某个类型的实例。正如C.41说明的:构造函数应该生成一个完全初始化的对象。不应该要求额外的初始化,例如使用memcpy。类型应该提供一个拷贝构造函数和/或者拷贝赋值运算符以便适当地生成类的拷贝并维持类的不变量。使用memcpy拷贝一个非平常可拷贝类型的行为没有定义。通常会导致断层或者数据破坏。
Example, good(范例)
struct base
{
virtual void update() = 0;
std::shared_ptr<int> sp;
};
struct derived : public base
{
void update() override {}
};
Example, bad(反面示例)
void init(derived& a)
{
memset(&a, 0, sizeof(derived));
}
This is type-unsafe and overwrites the vtable.
这个函数类型不安全而且会覆盖虚函数表。
Example, bad(反面示例)
void copy(derived& a, derived& b)
{
memcpy(&a, &b, sizeof(derived));
}
This is also type-unsafe and overwrites the vtable.
这个函数同样是类型不安全而且覆盖虚函数表。
Enforcement(实施建议)
Flag passing a non-trivially-copyable type to memset or memcpy.
提示向memset或memcpy传递非平凡可拷贝类型的处理!
//关于平凡拷贝请参见:
https://zh.cppreference.com/w/cpp/named_req/TriviallyCopyable