C++程序员应了解的那些事:C++ 核心准则之 ~ Classes and class hierarchies

 

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‍

 

 

 

 

 

<原链接>https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c83-for-value-like-types-consider-providing-a-noexcept-swap-function

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值