19

编辑:已解决见评论——不知道如何在没有答案的情况下标记为已解决。

在观看了有关 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& s 分配给它。通过使用适当的构造函数构造一个新对象,然后用 *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>

难道我做错了什么?这是不好的做法(我认为没有办法测试你是否是自我分配的)?编译器还没有准备好?

谢谢

4

3 回答 3

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

现在,您的客户可以在效率(我的 type::operator=)或使用strong_assign.

于 2011-09-17T22:20:14.880 回答
2

您是否仔细阅读了错误消息?它看到两个错误,即您有多个复制赋值运算符和多个移动赋值运算符。而且完全正确!

特殊成员最多只能指定一次,无论它们是默认的、删除的、常规定义的,还是被排除在外的隐式处理。您有两个复制赋值运算符(一个 take my_type,另一个taking my_type const &)和两个移动赋值运算符(一个taking my_type,另一个taking my_type &&)。请注意,赋值运算符my_type可以处理左值和右值引用,因此它既可以作为复制赋值,也可以作为移动赋值。

大多数特殊成员的函数签名有多种形式。你必须选择一个;你不能使用一个不寻常的,然后删除常规的,因为那将是一个双重声明。编译器将自动使用格式异常的特殊成员,并且不会合成具有常规签名的特殊成员。

(请注意,错误提到了三个候选。对于每种分配类型,它会看到适当的已删除方法、采用 的方法,my_type然后将另一个已删除的方法视为紧急近似匹配。)

于 2011-10-27T10:53:37.053 回答
0

您是否应该删除赋值运算符的那些重载?您对赋值运算符的声明不应该是模板或其他东西吗?我真的不明白这应该如何工作。

请注意,即使这样有效,通过以这种方式实现移动赋值运算符,刚刚被移动的对象所持有的资源将在其生命周期结束时释放,而不是在赋值时释放。有关更多详细信息,请参见此处:

http://cpp-next.com/archive/2009/09/your-next-assignment/

于 2011-09-17T22:06:11.700 回答