【问题标题】:What is the "correct" way to write the Copy/Move/operator= trio in C++11?在 C++11 中编写 Copy/Move/operator= trio 的“正确”方法是什么?
【发布时间】:2012-03-13 06:52:49
【问题描述】:

至此,编写复制构造函数和赋值操作符对就定义好了;快速搜索将引导您找到大量关于如何正确编码这些内容的信息。

既然移动构造函数已经加入,有没有新的“最佳”方式?

【问题讨论】:

  • 有一个很好的SO question,你可能想看看。
  • 这个问题太笼统了。您需要将其分解为特定场景。因为没有关于如何以“定义明确”的方式为每个类编写复制构造函数和赋值运算符的方法。你的问题也一样。
  • 为什么它太宽泛了?复制构造函数 + 赋值运算符有一个普遍接受的模式,为什么不移动构造函数呢?
  • 如果“复制构造函数 + 赋值运算符的普遍接受模式”是指“复制和交换”,请参阅此链接:stackoverflow.com/a/9322542/576911

标签: c++ c++11


【解决方案1】:

最好是= default;,因为成员类型应该是资源管理类型,对您隐藏移动细节,例如std::unique_ptr。只有那些“低级”类型的实现者才应该处理这个问题。

请记住,如果您持有一个外部(对您的对象)资源,您只需要关心移动语义。它对于“扁平”类型完全没用。

【讨论】:

  • 这是一个有效的观点。我想我可以用“如果你要自己写”来限定,但你的回答对于那些没有意识到他们不需要这样做的人来说是非常正确的。但是,就我而言,我持有一个外部资源,这就是产生这个问题的原因。 :)
  • 不过,小的重构可能会消除这个问题。我会考虑一下。不知道最终结果会不会更简单。
  • @NicolBolas 谢谢你的信息。伤心。
  • prefer unique_ptr 到 shared_ptr。两者都可以清理任意资源。
  • 实际上,= default 只有在所有子对象的组合移出状态满足包含类型的不变量时才对您有用,并且与子对象是否-objects 是“资源管理类型”。 通常,当包含类型是默认可构造的时会出现这种情况,但即便如此也可能不是。想出反例很容易。
【解决方案2】:

最好的办法是让编译器全部生成。这也是 C++03 中最好的方法,如果您设法做到这一点,那么当您迁移到 C++11 时,您的 C++03 类会自动变为“启用移动”。

大多数资源管理问题可以通过只编写单一资源管理类的非复制构造函数和析构函数来解决,然后只使用它们制作复合类,加上智能指针(例如std::unique_ptr)和容器类来构建更丰富对象。

【讨论】:

    【解决方案3】:

    使用clang/libc++:

    #include <chrono>
    #include <iostream>
    #include <vector>
    
    #if SLOW_DOWN
    
    class MyClass
    {
        void Swap(MyClass &other)
        {
            std::swap(other.member, member);
        }
    
    public:
        MyClass()
            : member()
        {
        }
    
        MyClass(const MyClass &other)
            : member(other.member)
        {
        }
    
        MyClass(MyClass &&other)
            : member(std::move(other.member))
        {
        }
    
        MyClass &operator=(MyClass other)
        {
            other.Swap(*this);
            return *this;
        }
    
    private:
        int member;
    };
    
    #else
    
    class MyClass
    {
    public:
        MyClass()
            : member()
        {
        }
    
    private:
        int member;
    };
    
    #endif
    
    int main()
    {
        typedef std::chrono::high_resolution_clock Clock;
        typedef std::chrono::duration<float, std::milli> ms;
        auto t0 = Clock::now();
        for (int k = 0; k < 100; ++k)
        {
            std::vector<MyClass> v;
            for (int i = 0; i < 1000000; ++i)
                v.push_back(MyClass());
        }
        auto t1 = Clock::now();
        std::cout << ms(t1-t0).count() << " ms\n";
    }
    
    $ clang++ -stdlib=libc++ -std=c++11 -O3 -DSLOW_DOWN test.cpp 
    $ a.out
    519.736 ms
    $ a.out
    517.036 ms
    $ a.out
    524.443 ms
    
    $ clang++ -stdlib=libc++ -std=c++11 -O3  test.cpp 
    $ a.out
    463.968 ms
    $ a.out
    458.702 ms
    $ a.out
    464.441 ms
    

    在本次测试中,这看起来大约有 12% 的速度差异。

    说明:这些定义之一有一个普通的复制构造函数和复制赋值运算符。另一个没有。 “琐碎”在 C++11 中具有真正的意义。这意味着允许实现使用memcpy 来复制您的类。或者甚至复制您班级的大型数组。因此,如果可以的话,最好让您的特殊成员变得微不足道。这意味着让编译器定义它们。如果您愿意,您仍然可以使用= default 声明它们。

    【讨论】:

    • Howard, SLOW_DOWN 只是一个非常低效的版本。这甚至与编译器生成的构造函数等的比较无关......这是 ideone.com 上的两个代码:first one 使用 OP 的版本T &amp; operator( T )second 使用明确定义的T &amp;( T &amp;&amp; )T &amp; operator=( T &amp;&amp; )。比较-----------之间的输出...
    • @AzzA:我只是将 OP 的解决方案与我认为更好的解决方案进行比较。不多也不少。
    【解决方案4】:

    这是我想出的,但我不知道是否有更优化的解决方案。

    class MyClass
    {
        void Swap(MyClass &other)
        {
            std::swap(other.member, member);
        }
    
    public:
        MyClass()
            : member()
        {
        }
    
        MyClass(const MyClass &other)
            : member(other.member)
        {
        }
    
        MyClass(MyClass &&other)
            : member(std::move(other.member))
        {
        }
    
        MyClass &operator=(MyClass other)
        {
            other.Swap(*this);
            return *this;
        }
    
    private:
        int member;
    };
    

    【讨论】:

    • 为什么要将交换设为私有?
    • @ronag:在我的真实课堂上,还没有真正需要它。我宁愿不公开以后有人可能会出现并使用的 API,可能会不正确(尽管我不知道他们如何滥用像交换这样简单的东西)。
    • 如果MyClass 真的应该具有这些语义,这可能是编写特殊成员的最糟糕(性能最差)的方式,但仍然被认为是正确的。抱歉,这么直接,但我认为你应该知道。
    • @mos 你真的考虑过当你做MyClass lC = MyClass(), lC1; lC1 = lC;这样的事情时会发生什么吗?
    • @ronag: Touche :) 没有真正的理由将其保密。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-28
    • 2010-11-22
    • 2017-04-20
    • 1970-01-01
    相关资源
    最近更新 更多