重用复制和交换习惯用法

发布于 2024-11-29 17:29:06 字数 731 浏览 4 评论 0原文

我正在尝试将复制和交换习惯用法放入可重用的 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;

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

I'm trying to put the copy-and-swap idiom into a reusable mixin:

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

I intend it to be mixed in via 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";
    }
};

However, a simple test shows that it is not working:

Foo x;
Foo y;
x = y;

This only prints "default" twice, neither "copy" nor "swap" is printed. What am I missing here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

千鲤 2024-12-06 17:29:06

这:

 Derived& operator=(Derived copy)

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

记住12.8:

用户声明的复制赋值运算符 X::operator= 是非静态的
类 X 的非模板成员函数,只有一个参数为
类型 X、X&、const X&、易失性 X&或 const 易失性 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 赋值运算符,一次通过派生类生成的赋值运算符。要纠正这种情况,您必须执行以下操作(并且不要忘记):

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 类嗯>。

This:

 Derived& operator=(Derived copy)

doesn't declare a copy assignment operator for the base class (it has the wrong signature). So the default generated assignment operator in Foo will not use this operator.

Remember 12.8:

A user-declared copy assignment operator X::operator= is a non-static
non-template member function of class X with exactly one parameter of
type X, X&, const X&, volatile X& or const volatile X&.) [Note: an
overloaded assignment operator must be declared to have only one
parameter; see 13.5.3. ] [Note: more than one form of copy assignment
operator may be declared for a class. ] [Note: if a class X only has a
copy assignment operator with a parameter of type X&, an expression of
type const X cannot be assigned to an object of type X.

EDIT don't do this (can you see why ?):

You can do:

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

but you lose the potential copy elision optimization.

Indeed, this would assign twice the members of derived classes: once via copy_and_swap<Derived> assignment operator, and once via the derived class' generated assignment operator. To correct the situation, you'd have to do (and not forget to do):

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
}

The moral of the story: don't write a CRTP class for the copy and swap idiom.

猫卆 2024-12-06 17:29:06

如果内存正确的话,您不能将赋值运算符作为特殊情况继承。我相信如果您需要的话,可以显式使用它们。

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

You cannot inherit assignment operators as a special case, if memory correctly serves. I believe that they can be explicitly using'd in if you need.

Also, be careful about over use of copy-and-swap. It produces non-ideal results where the original has resources that could be re-used to make the copy, such as containers. Safety is guaranteed but optimum performance is not.

可爱咩 2024-12-06 17:29:06

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

无论您做什么,您都会遇到以下两种情况之一:

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

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

复制和交换仅用于非常特定的类。我认为这不值得。

I am afraid this is one area where a macro is necessary, because of the complex rules about automatically generated copy and assignment operators.

No matter what you do, you are in either of two cases:

  • You have provided (explicitly) a declaration of the assignment operator, in which case you are expected to provide a definition too
  • You have not provided (explicitly) a declaration of the assignment operator, in which case the compiler will generate one if the base classes and non-static members have one available.

The next question, therefore, is: Is it worth it to automate such writing ?

Copy-And-Swap is only used for very specific classes. I do not think it's worth it.

油焖大侠 2024-12-06 17:29:06

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

    using copy_and_swap<Foo>::operator=;

到 Foo,您将看到一个错误,告诉您 g++ 上的歧义。

The compiler automatically generates a copy assignment operator for Foo, since there is none.
If you add a

    using copy_and_swap<Foo>::operator=;

to Foo you will see an error telling you about the ambiguity on g++.

如梦亦如幻 2024-12-06 17:29:06

也许您可以重写它,使其看起来像这样:

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

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

Maybe you could rewrite it so it looks like so:

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

It'll probably all get inlined and likely won't be any slower than the original code.

梦断已成空 2024-12-06 17:29:06

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

This does not really answer the question (@Alexandre C. already did), but if you reverse the inheritance, you could make it work:

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文