在c ++ 0x中观看了关于完美转发/移动语义的第9频道视频后,我发现了一些相信这是编写新赋值运算符的好方法。
#include <string>
#include <vector>
#include <iostream>
struct my_type
{
my_type(std::string name_)
: name(name_)
{}
my_type(const my_type&)=default;
my_type(my_type&& other)
{
this->swap(other);
}
my_type &operator=(my_type other)
{
swap(other);
return *this;
}
void swap(my_type &other)
{
name.swap(other.name);
}
private:
std::string name;
void operator=(const my_type&)=delete;
void operator=(my_type&&)=delete;
};
int main()
{
my_type t("hello world");
my_type t1("foo bar");
t=t1;
t=std::move(t1);
}
这应该允许r值和const&amp;分配给它。通过使用适当的构造函数构造一个新对象,然后使用* this交换内容。这对我来说听起来很合理,因为没有数据被复制超过它需要的数量。指针算法很便宜。
但是我的编译器不同意。 (g ++ 4.6)我得到了这些错误。
copyconsttest.cpp: In function ‘int main()’:
copyconsttest.cpp:40:4: error: ambiguous overload for ‘operator=’ in ‘t = t1’
copyconsttest.cpp:40:4: note: candidates are:
copyconsttest.cpp:18:11: note: my_type& my_type::operator=(my_type)
copyconsttest.cpp:30:11: note: my_type& my_type::operator=(const my_type&) <deleted>
copyconsttest.cpp:31:11: note: my_type& my_type::operator=(my_type&&) <near match>
copyconsttest.cpp:31:11: note: no known conversion for argument 1 from ‘my_type’ to ‘my_type&&’
copyconsttest.cpp:41:16: error: ambiguous overload for ‘operator=’ in ‘t = std::move [with _Tp = my_type&, typename std::remove_reference< <template-parameter-1-1> >::type = my_type]((* & t1))’
copyconsttest.cpp:41:16: note: candidates are:
copyconsttest.cpp:18:11: note: my_type& my_type::operator=(my_type)
copyconsttest.cpp:30:11: note: my_type& my_type::operator=(const my_type&) <deleted>
copyconsttest.cpp:31:11: note: my_type& my_type::operator=(my_type&&) <deleted>
我做错了吗?这是不好的做法(我认为没有办法测试你是否自我分配)?编译器还没有准备好吗?
由于
答案 0 :(得分:24)
对副本/交换分配习惯用语非常谨慎。它可能是次优的,特别是在没有仔细分析的情况下应用时。即使您需要强大的异常安全性,也可以获得该功能。
我推荐你的例子:
struct my_type
{
my_type(std::string name_)
: name(std::move(name_))
{}
void swap(my_type &other)
{
name.swap(other.name);
}
private:
std::string name;
};
这将为您提供隐式复制和移动语义,这些语义转发到std :: string的复制和移动成员。 std :: string的作者最了解如何完成这些操作。
如果你的编译器还不支持隐式移动生成,但支持默认的特殊成员,你可以这样做:
struct my_type
{
my_type(std::string name_)
: name(std::move(name_))
{}
my_type(const mytype&) = default;
my_type& operator=(const mytype&) = default;
my_type(mytype&&) = default;
my_type& operator=(mytype&&) = default;
void swap(my_type &other)
{
name.swap(other.name);
}
private:
std::string name;
};
如果您只想明确了解您的特殊会员,您也可以选择执行上述操作。
如果您正在处理尚未支持默认特殊成员(或隐式移动成员)的编译器,那么您可以明确地提供编译器在完全符合C ++ 11时最终应该默认的内容:
struct my_type
{
my_type(std::string name_)
: name(std::move(name_))
{}
my_type(const mytype& other)
: name(other.name) {}
my_type& operator=(const mytype& other)
{
name = other.name;
return *this;
}
my_type(mytype&& other)
: name(std::move(other.name)) {}
my_type& operator=(mytype&& other)
{
name = std::move(other.name);
return *this;
}
void swap(my_type &other)
{
name.swap(other.name);
}
private:
std::string name;
};
如果您确实需要强大的异常安全性,请将其设计一次并明确说明(编辑以包含Luc Danton的建议):
template <class C>
typename std::enable_if
<
std::is_nothrow_move_assignable<C>::value,
C&
>::type
strong_assign(C& c, C other)
{
c = std::move(other);
return c;
}
template <class C>
typename std::enable_if
<
!std::is_nothrow_move_assignable<C>::value,
C&
>::type
strong_assign(C& c, C other)
{
using std::swap;
static_assert(std::is_nothrow_swappable_v<C>, // C++17 only
"Not safe if you move other into this function");
swap(c, other);
return c;
}
现在,您的客户可以使用strong_assign
在效率(我的类型::运算符=)或强异常安全性之间进行选择。
答案 1 :(得分:2)
您是否仔细阅读了错误消息?它会看到两个错误,即您有多个复制赋值运算符和多个移动赋值运算符。这是完全正确的!
特殊成员必须最多指定一次,无论他们是否被默认,删除,按常规定义,或通过被遗漏而隐式处理。您有两个复制赋值运算符(一个使用my_type
,另一个使用my_type const &
)和两个移动赋值运算符(一个采用my_type
,另一个采用my_type &&
)。请注意,使用my_type
的赋值运算符可以处理左值和右值引用,因此它可以作为复制和移动赋值。
大多数特殊成员的功能签名有多种形式。你必须选一个;你不能使用一个不寻常的,然后删除传统的,因为这将是一个双重声明。编译器将自动使用异常形成的特殊成员,并且不会使用传统签名合成特殊成员。
(请注意,错误提到了三个候选。对于每个分配类型,它会看到相应的已删除方法,采用my_type
的方法,然后将另一个已删除的方法视为紧急匹配。)
答案 2 :(得分:0)
你应该删除赋值运算符的那些重载吗?您的赋值运算符声明不应该是模板或其他东西吗?我真的不明白这是怎么回事。
请注意,即使这样可行,通过以这种方式实现移动赋值运算符,刚刚移动的对象所拥有的资源将在其生命周期结束时释放,而不是在赋值时释放。有关详细信息,请参见此处: