【问题标题】:reusing the copy-and-swap idiom重用复制和交换习语
【发布时间】:2011-10-28 03:38:27
【问题描述】:

我正在尝试将复制和交换习语放入可重用的 mixin:

template<typename Derived>
struct copy_and_swap
{
    Derived& operator=(Derived copy)
    {
        Derived* derived = static_cast<Derived*>(this);
        derived->swap(copy);
        return *derived;
    }
};

我打算通过 CRTP 将其混入:

struct Foo : copy_and_swap<Foo>
{
    Foo()
    {
        std::cout << "default\n";
    }

    Foo(const Foo& other)
    {
        std::cout << "copy\n";
    }

    void swap(Foo& other)
    {
        std::cout << "swap\n";
    }
};

但是,一个简单的测试表明它不起作用:

Foo x;
Foo y;
x = y;

这只会打印两次“default”,既不打印“copy”也不打印“swap”。我在这里错过了什么?

【问题讨论】:

  • 也许编译器提供了他自己的operator= 版本,因为你的Foo 类中缺少一个?也许你必须做Foo::operator=(){return copy_and_swap();}(伪代码)?

标签: c++ assignment-operator mixins crtp copy-and-swap


【解决方案1】:

这个:

 Derived& operator=(Derived copy)

没有为基类声明一个复制赋值运算符(它的签名错误)。所以Foo中默认生成的赋值运算符不会使用这个运算符。

记住 12.8:

用户声明的复制赋值运算符 X::operator= 是非静态的 类 X 的非模板成员函数,只有一个参数 类型 X、X&、const X&、volatile X& 或 const volatile X&。)[注:an 重载的赋值运算符必须声明为只有一个 范围;见 13.5.3。 ] [注:多于一种形式的复制分配 可以为类声明运算符。 ] [注意:如果一个类 X 只有一个 复制赋值运算符,参数类型为 X&,表达式为 const X 类型不能分配给 X 类型的对象。

编辑不要这样做(你明白为什么吗?):

你可以这样做:

template<typename Derived>
struct copy_and_swap
{
    void operator=(const copy_and_swap& copy)
    {
        Derived copy(static_cast<const Derived&>(copy));
        copy.swap(static_cast<Derived&>(*this));
    }
};

但你失去了潜在的复制省略优化。

确实,这将分配两次派生类的成员:一次通过copy_and_swap&lt;Derived&gt; 赋值运算符,一次通过派生类生成的赋值运算符。要纠正这种情况,您必须这样做(并且不要忘记这样做):

struct Foo : copy_and_swap<Foo>
{

    Foo& operator=(const Foo& x)
    {
        static_cast<copy_and_swap<Foo>&>(*this) = x;
        return *this;
    }

private:
    // Some stateful members here
}

故事的寓意:不要为复制和交换习语编写 CRTP 类

【讨论】:

  • 如果你 = delete 编译器生成的 Foo 赋值运算符(假设 C++0x)怎么办?
  • = delete 将禁止在 Foo 上调用 operator=,这会适得其反。
  • @R. Martinho:不,是你强制基类给派生类的成员赋值,而派生类的默认赋值运算符会给成员赋值。所以最后,你分配了两次。
  • @Mike - 如果你有 c++0x,你不会只使用“移动构造函数”吗?
【解决方案2】:

如果内存正常,您不能将赋值运算符作为特殊情况继承。我相信如果您需要,他们可以明确地using'd。

另外,请注意过度使用复制和交换。它会产生不理想的结果,其中原始文件具有可重复用于制作副本的资源,例如容器。安全得到保证,但最佳性能却没有。

【讨论】:

  • struct Foo 中添加using copy_and_swap&lt;Foo&gt;::operator=; 并不能纠正这种情况。见Alexandre C.'s answer
【解决方案3】:

恐怕这是一个需要宏的领域,因为关于自动生成的复制和赋值运算符的复杂规则。

无论你做什么,你都处于两种情况中的任何一种:

  • 您已经(明确)提供了赋值运算符的声明,在这种情况下,您也应该提供定义
  • 您没有(明确地)提供赋值运算符的声明,在这种情况下,如果基类非静态成员有一个可用的,编译器将生成一个。

因此,下一个问题是:自动化这样的写作值得吗?

Copy-And-Swap 仅用于非常特定的类。我觉得不值得。

【讨论】:

    【解决方案4】:

    编译器会自动为 Foo 生成一个复制赋值运算符,因为没有。 如果你添加一个

        using copy_and_swap<Foo>::operator=;
    

    你会看到一个错误告诉你关于 g++ 的歧义。

    【讨论】:

    • 我试过了,没有任何歧义。但是,操作符仍然没有被调用。
    • @André Caron:我又检查了一遍:g++ 没有 msvc。
    【解决方案5】:

    也许你可以重写它,让它看起来像这样:

    template<class Derived>
    struct CopySwap
    {
      Dervied &operator=(Derived const &other)
      {
        return AssignImpl(other);
      }
    
      Derived &operator=(Dervied &&other)
      {
        return AssignImpl(std::move(other));
      }
    
    private:
      Derived &AssignImpl(Derived other)
      {
        auto self(static_cast<Derived*>(this));
        self->swap(other);
        return *self;
      }
    };
    

    它可能会全部内联,并且可能不会比原始代码慢。

    【讨论】:

      【解决方案6】:

      这并不能真正回答问题(@Alexandre C. already did),但如果你反转继承,你可以让它工作:

      template<typename Base>
      struct copy_and_swap : Base
      {
          copy_and_swap& operator=(copy_and_swap copy)
          {
              swap(copy);
              return *this;
          }
      };
      
      struct Foo_
      {
          Foo_()
          {
              std::cout << "default\n";
          }
      
          Foo_(const Foo_& other)
          {
              std::cout << "copy\n";
          }
      
          void swap(Foo_& other)
          {
              std::cout << "swap\n";
          }
      };
      
      typedef copy_and_swap<Foo_> Foo;
      
      int main()
      {
          Foo x;
          Foo y;
          x = y;
      }
      

      【讨论】:

        猜你喜欢
        • 2023-03-23
        • 1970-01-01
        • 2014-06-23
        • 2011-11-22
        • 2016-02-17
        • 2015-08-04
        • 2012-02-09
        • 2020-05-27
        • 2020-02-29
        相关资源
        最近更新 更多