【问题标题】:How to implement a c++11 move function for a user-defined class?如何为用户定义的类实现 c++11 移动函数?
【发布时间】:2014-06-29 13:27:29
【问题描述】:

我有一个用户定义的类(树结构),实现了移动语义和一个swap 函数。我想以正确的方式实现 move 函数,作为标准 std::move 实现。

在树节点类中,每个子节点都有一个parent指针,指向父节点。这意味着对于移动操作,所有子节点都必须重新设置父节点(并且可能有很多子节点)

这意味着使用swap 进行移动不是最佳选择,因为两个列表的子级在交换后必须重新设置父级。所以我想实现一个move 函数来清除移动的树。

std::move 实现的声明有些复杂,它们使用std::remove_reference<T>::type&& 返回类型。我需要这个吗?

【问题讨论】:

  • 这个很不清楚。单参数std::move 只是一个演员表,你不需要实现任何东西。无论如何,内部swap 通常都适用于左值引用。
  • remove_reference 很简单。见en.cppreference.com/w/cpp/types/remove_reference
  • 如果你的编译器支持decltype(auto)(一个C++14特性AFAIK),你可以稍微简化一下定义(见this talk by Scott Meyers大约6.5分钟)

标签: c++ c++11 move move-semantics


【解决方案1】:

您不需要编写 std::move 的特化。

如果您编写了正确的移动构造函数和移动赋值运算符,std::move 将适用于您的类。

示例:

#include <iostream>
#include <cstring>

using namespace std;

struct Thing {
    Thing()
    : _data(new int[100])
    {
        cout << "default construct\n";

    }

    // Copy operator
    Thing(const Thing& other)
    : _data(new int[100])
    {
        cout << "copy constructor\n";
        memcpy(_data, other._data, sizeof(int) * 100);
    }

    // Move constructor
    Thing(Thing&& other) noexcept
    : _data(other._data)
    {
        cout << "move constructor\n";
        other._data = nullptr;
    }

    // assignment operator
    Thing& operator=(const Thing& rhs) {
        cout << "copy operator\n";
        if (&rhs != this) {
            Thing tmp(rhs);
            std::swap(*this, tmp);
        }
        return *this;
    }

    // move assignment operator
    Thing& operator=(Thing&& rhs) noexcept {
        cout << "move operator\n";
        std::swap(_data, rhs._data);
        return *this;
    }


    // destructor necessary since we are working in dangerous new/delete territory
    ~Thing() noexcept {
        cout << "destructor " << (_data ? "object has data" : "object is empty") << "\n";

        delete[] _data;
    }
private:
    int* _data;
};
int main()
{
    cout << "constructing a\n";
    Thing a;

    cout << "constructing b with copy of a\n";
    Thing b(a);

    cout << "moving a to newly constructed c\n";
    Thing c(std::move(a));

    cout << "moving c back to a\n";
    a = std::move(c);

    cout << "create a new d\n";
    Thing d;
    cout << "replace d with a copy of a\n";
    d = a;

    return 0;
}

程序的输出:

constructing a
default construct
constructing b with copy of a
copy constructor
moving a to newly constructed c
move constructor
moving c back to a
move operator
create a new d
default construct
replace d with a copy of a
copy operator
copy constructor
move constructor
move operator
move operator
destructor object is empty
destructor object has data
destructor object has data
destructor object is empty
destructor object has data
destructor object has data

【讨论】:

  • 我从 std::move 确实有效地交换标准容器的事实开始,这是我不想要的。所以 std::move 只有在我的移动 ctor/assignment 交换时才会交换,如果它们移动则移动?有道理
  • std::move 简单地将对象引用转换为 R 值引用,允许选择对象的移动构造函数或移动运算符。我给你写一个小例子。
  • 你去。提供的示例。编译它并单步执行。
  • 别忘了noexcept。许多标准库如果不能保证移动是不扔的,就会退化为更安全的复制语义。
  • 从头开始编写一个指针控制类的完整实现是健康的 - 它再次强调了为什么我们不应该这样做,因为在没有错误的情况下很难做到这一点,而 std::unique_ptr 和std::shared_ptr 用指针完美安全地做所有你想做的事情。
【解决方案2】:

写move语义就是写move构造函数/move赋值,而不是实现move函数。结果,您可能会在 move ctor/assignment 中移动另一棵树的根。

class binary_tree_node {};

class binary_tree
{
public:
    binary_tree() : root(nullptr) {}

    binary_tree(binary_tree &&rhs)
        : root(rhs.root)
    {
        rhs.root = nullptr;
    }

    binary_tree& operator=(binary_tree rhs)
    {
        swap(rhs);
        return *this;
    }

    void swap(binary_tree &rhs)
    {
        std::swap(root, rhs.root);
    }

private:
    binary_tree_node *root;
};

int main()
{
    binary_tree tree1;
    binary_tree tree2 = std::move(tree1);

    return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多