C++统一分配运算符移动语义

111*_*111 19 c++ rvo perfect-forwarding c++11

编辑:解决看到评论 - 不知道如何标记解决与答案.

在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);
}
Run Code Online (Sandbox Code Playgroud)

这应该允许将r值和const分配给它.通过使用适当的构造函数构造一个新对象,然后使用*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>
Run Code Online (Sandbox Code Playgroud)

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

谢谢

How*_*ant 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;
};
Run Code Online (Sandbox Code Playgroud)

这将获得隐式复制和移动语义,这些语义转发到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;
};
Run Code Online (Sandbox Code Playgroud)

如果您只是想明确您的特殊成员,您也可以选择执行上述操作.

如果您正在处理尚未支持默认特殊成员(或隐式移动成员)的编译器,那么您可以显式提供编译器在完全符合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;
};
Run Code Online (Sandbox Code Playgroud)

如果你真的需要强大的异常安全性,可以设计一次并明确它(编辑包括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;
}
Run Code Online (Sandbox Code Playgroud)

现在,您的客户可以选择效率(我的类型::运算符=)或强大的异常安全性strong_assign.

  • @111111:你会知道编译器在做什么,因为它是完全指定的。就像通过复制每个基础和成员来指定默认副本一样,通过移动每个基础和成员来指定默认移动。如果这对您的类来说是正确的,那么请使用默认值,否则您自己编写或禁用它(与处理副本没有什么不同)。对于像“std::string”这样的类型,您应该能够指望它们具有快速移动构造函数和移动赋值。如果您不确定,请测量。有一个很棒的新“&lt;chrono&gt;”标头,使精确计时变得非常简单!:-) (2认同)