我正在尝试将复制和交换习惯用法放入可重复使用的混音中:
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;
这只打印两次“默认”,既不打印“复制”也不打印“交换”。我在这里缺少什么?
答案 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;
}