这是否正确实施了五法则(或四法则和 1/2)?

Is this a proper implementation of the Rule of Five (or Rule of Four and 1/2)?

提问人:Rsevero 提问时间:1/30/2022 更新时间:1/30/2022 访问量:271

问:

我正在研究五法则和它的表亲(四法则和 1/2、复制和交换成语、朋友交换函数)。

我在测试课上实施了四法则和 1/2。它编译得很好。我的实施中是否有任何隐藏的错误?

我特别关注存储在复制构造函数中移动的 m_unorederd_map 属性中的unique_ptrs,因为它们无法复制。这是在课堂上处理unique_ptrs的正确方法吗?

某一类.h

#ifndef SOMECLASS_H
#define SOMECLASS_H

#include "someotherclass.h"

#include <QString>
#include <QStringList>

#include <memory>
#include <string>
#include <unordered_map>

class SomeClass
{
    QString m_qstring;
    std::unordered_map<std::string, std::unique_ptr<SomeOtherClass>> m_unordered_map;
    int m_int;
    std::string m_string;
    QStringList m_qstringlist;

public:
    SomeClass() = default;

    // Rule of 5 (or Rule of 4 and 1/2)
    // From https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom#3279550
    ~SomeClass() = default;                               // Destructor
    SomeClass(SomeClass &other);                          // Copy constructor
    SomeClass(SomeClass &&other);                         // Move constructor
    SomeClass &operator=(SomeClass other);                // Copy/Move assignment operator
    friend void swap(SomeClass &first, SomeClass &second) // Friend swap function
    {
        using std::swap;
        first.m_qstring.swap(second.m_qstring);
        first.m_unordered_map.swap(second.m_unordered_map);
        swap(first.m_int, second.m_int);
        swap(first.m_string, second.m_string);
        first.m_qstringlist.swap(second.m_qstringlist);
    }
};

#endif // SOMECLASS_H

someclass.cpp

#include "someclass.h"

// Copy constructor
SomeClass::SomeClass(SomeClass &other)
    : m_qstring(other.m_qstring),
      m_int(other.m_int),
      m_string(other.m_string),
      m_qstringlist(other.m_qstringlist)
{
    // m_unordered_map holds unique_ptrs which can't be copied.
    // So we move it.
    m_unordered_map = std::move(other.m_unordered_map);
}

// Move constructor
SomeClass::SomeClass(SomeClass &&other)
    : SomeClass()
{
    swap(*this, other);
}

// Copy/Move assignment operator
SomeClass &SomeClass::operator=(SomeClass other)
{
    swap(*this, other);
    return *this;
}
C++ Qt 无序映射 五法则

评论

0赞 Ted Lyngmo 1/30/2022
Qt真的重要吗?我怀疑它不是,你可以删除它以使问题更清晰。
1赞 user17732522 1/30/2022
无论实现是否正确:据我所知,没有理由不遵循零规则。 是一个很好的赠品。~SomeClass() = default;
1赞 Paul Sanders 1/30/2022
从中移动的复制构造函数不是很用户友好。other
0赞 PaulMcKenzie 1/30/2022
它编译得很好 - 通常新的发帖人/程序员会做出这种不必要的评论。“编译良好”除了没有语法错误之外,绝对没有任何意义。程序是否正常运行,与程序是否“编译良好”无关。
1赞 PaulMcKenzie 1/30/2022
@Rsevero -- 如果这些Qt类已经有正确的复制语义,那么你所要做的就是通过添加不必要的复制函数来使你的代码面临错误的风险。有人添加了另一个成员变量,然后您必须更改代码以记住处理该成员变量。现在有人添加了 10 个成员变量,只记得处理其中的 9 个——bug。

答:

6赞 HolyBlackCat 1/30/2022 #1

最重要的是:

  • 此类不需要自定义复制/移动操作,也不需要析构函数,因此应遵循 0 规则。

其他事项:

  • 我不喜欢搬家。它强制默认构造成员,然后分配成员。更好的替代方法是使用成员初始值设定项列表,其中 .swap(*this, other);std::exchange

    如果初始化所有成员变得乏味,请将它们包装在一个结构中。它也使写作变得更容易。swap

  • 复制构造函数必须通过常量引用来获取参数。

  • unique_ptrs which can't be copied. So we move it.是一个不好的理由。如果无法复制成员,请不要定义复制操作。如果存在自定义移动操作,则不会自动生成复制操作

  • 移动操作(包括按值赋值)应为 ,因为在某些情况下,标准容器不会使用它们。noexcept

  • SomeClass() = default;导致通常未初始化 () 的成员有时为零,具体取决于类的构造方式。(例如 将其归零,但不会。int m_int;SomeClass x{};SomeClass x;

    除非您想要此行为,否则构造函数应替换为 ,并且可能应该归零(在类体中)。SomeClass() {}m_int

评论

0赞 Rsevero 1/30/2022
即使是unique_ptrs unordered_map也不会要求自定义移动操作?std::move(或在规则 4 和 1/2 的情况下交换)或类似的东西将被编译器自动包含吗?
0赞 HolyBlackCat 1/30/2022
@Rsevero 是的!默认情况下,编译器执行成员级操作(默认移动构造函数调用所有成员的移动构造函数,依此类推)。