11

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

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”。我在这里想念什么?

4

6 回答 6

7

这:

 Derived& operator=(Derived copy)

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

记住 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<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
}

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

于 2011-08-16T14:53:02.740 回答
1

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

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

于 2011-08-16T14:50:52.177 回答
0

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

    using copy_and_swap<Foo>::operator=;

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

于 2011-08-16T14:54:27.267 回答
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;
  }
};

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

于 2011-08-16T15:12:57.127 回答
0

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

无论您做什么,您都处于以下两种情况之一:

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

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

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

于 2011-08-16T15:50:24.610 回答
0

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

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;
}
于 2011-08-16T15:56:06.827 回答