重用复制和交换习语

时间:2011-08-16 14:48:07

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

我正在尝试将复制和交换习惯用法放入可重复使用的混音中:

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;

这只打印两次“默认”,既不打印“复制”也不打印“交换”。我在这里缺少什么?

6 个答案:

答案 0 :(得分:7)

此:

 Derived& operator=(Derived copy)

为基类声明一个副本赋值运算符(它的签名错误)。因此Foo中默认生成的赋值运算符将不使用此运算符。

记住12.8:

  

用户声明的复制赋值运算符X :: operator =是非静态的   类X的非模板成员函数,其中只有一个参数   类型X,X&amp;,const X&amp;,volatile X&amp;或const volatile X&amp ;.)[注意:a   必须将重载赋值运算符声明为只有一个   参数;见13.5.3。 ] [注意:多种形式的复制分配   可以为一个类宣布运算符。 ] [注意:如果一个X类只有一个   复制赋值运算符,其参数类型为X&amp;,表达式为   类型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<Derived>赋值运算符,一次通过派生类'生成赋值运算符。要纠正这种情况,你必须这样做(不要忘记做):

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
}

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

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

    using copy_and_swap<Foo>::operator=;

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

答案 3 :(得分:0)

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

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;
  }
};

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

答案 4 :(得分:0)

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

无论你做什么,你都有两种情况:

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

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

Copy-And-Swap仅用于非常特定的类。我不认为这是值得的。

答案 5 :(得分:0)

这并没有真正回答问题(@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;
}