【问题标题】:Using move semantics to avoid copying when pushing_back to a custom container does not avoid copyingpush_back 到自定义容器时使用移动语义避免复制并不能避免复制
【发布时间】:2018-05-18 23:00:01
【问题描述】:

我已经实现了一个自定义容器(与 std::vector 相同),并且我正在尝试制作它,以便它的“push_back”功能将利用移动语义来避免创建被推回的任何内容的副本 -特别是当要推入容器的对象由外部函数返回时。

在阅读了大量有关移动语义和自定义容器的内容后,我仍然无法找到为什么我的方法仍然生成副本而不是仅仅将传递的对象移动到容器的内部动态数组中。

这是我的容器的简化版本,如下所示:

template<class T>
class Constructor
{
private:
    size_t size = 0;
    size_t cap = 0;
    T *data = nullptr;

public:
Constructor()
{
    cap = 1;
    size = 0;
    data = static_cast<T*>(malloc(cap * sizeof(T)));
}

~Constructor()
{ delete[] data; }

template<typename U>
void push_back(U &&value)
{
    if (size + 1 >= cap)
    {
        size_t new_cap = (cap * 2);
        T* new_data = static_cast<T*>(malloc(new_cap * sizeof(T)));
        memmove(new_data, data, (size) * sizeof(T));
        for (size_t i = 0; i<cap; i++)
        {
            data[i].~T();
        }

        delete[] data;

        cap = new_cap;
        data = new_data;
        new(data + size) T(std::forward<U>(value));
    }
    else
    {
        new(data + size) T(std::forward<U>(value));
    }

    ++size;
}

const T& operator[](const size_t index) const //access [] overloading
{
    return data[index];
}
};

这是一个自定义类,它会在创建、复制或移动其实例时打印消息,以帮助调试:

class MyClass
{
size_t id;

public:
MyClass(const size_t new_id)
{
    id = new_id;
    std::cout << "new instance with id " << id << std::endl;
}
MyClass(const MyClass &passedEntity)
{
    id = passedEntity.id;
    std::cout << "copied instance" << std::endl;
}
MyClass(MyClass &&passedEntity)
{
    id = passedEntity.id;
    std::cout << "moved instance" << std::endl;
}

void printID() const
{
    std::cout << "this instance's id is " << id << std::endl;
}
};

这里是外部函数:

MyClass &foo(MyClass &passed)
{
    return passed;
}

最后,这里是 main 函数,它使用上述函数和类运行测试用例来显示问题:

int main()
{
MyClass a(33);
std::cout << std::endl;

std::cout << "Using my custom container: " << std::endl;
Constructor<MyClass> myContainer;
myContainer.push_back(foo(a));
myContainer[0].printID();
std::cout << std::endl;

std::cout << "Using dinamic array: " << std::endl;
MyClass *dinArray = static_cast<MyClass*>(malloc(1 * sizeof(MyClass)));
dinArray = new(dinArray + 1) MyClass(std::forward<MyClass>(foo(a)));
dinArray[0].printID();
std::cout << std::endl;


system("Pause");
return 0;
}

输出是:

new instance with id 33

Using my custom container:
copied instance
this instance's id is 33

Using dinamic array:
moved instance
this instance's id is 33

可以看出,如果将MyClass 的实例直接放入动态数组中,那么只调用了移动构造函数而不是复制构造函数。但是,如果我将 yClass 实例 push_back 到 Container 的实例中,仍然会调用复制构造函数。

有人可以帮我理解我在这里到底做错了什么吗?我怎样才能使元素被推入容器而不生成副本?

【问题讨论】:

  • 这就是 c++11 的 emplace_back() 背后的基本原理。
  • 如果您想在 C++03 中模拟移动语义,请使用 std::swap:void emplace(vector&lt;T&gt;&amp; v, T&amp; value) { v.resize(v.size()+ 1); std::swap(v.back(), value); } 您可能还必须为某些类型定义 std::swap。某种伪移动构造函数。

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


【解决方案1】:

当您拨打此行时

myContainer.push_back(foo(a));

L-value 被传递到 push_back 方法,现在阅读有关使用 std::forward - http://www.cplusplus.com/reference/utility/forward/

如果 arg 不是左值引用,则返回对 arg 的右值引用。

如果 arg 是左值引用,则函数返回 arg 而不修改其类型。

在你的push_back你打电话

new(data + size) T(std::forward<U>(value));

但是value是作为左值传递的,只能调用构造函数MyClass(const MyClass &amp;passedEntity)

如果你想移动a对象,你可以写

myContainer.push_back(std::move(a)); // cast to R-reference

编辑

您不应该在 push_back 函数中使用 move,下面是简单的示例。 假设你有这样的课程:

 struct Foo {
 int i;
 Foo (int i = 0) : i(i) { 
 }
 ~Foo () { 
 }
 Foo (const Foo& ) { 
 }
 Foo& operator=(const Foo&) { 
    return *this;
 }
 Foo (Foo&& f) 
 { 
    i = f.i; 
    f.i = 0;   // this is important
 }
 Foo& operator=(Foo&& f) 
 {   
    i = f.i; 
    f.i = 0; // this is important
    return *this;
 }
};

我们还有两个功能

template<class T> 
void process1 (const T& ) {
    cout << "process1" << endl;
}

template<class T>
void process (T&& obj) {
    cout << "process2" << endl;
    T newObj = forward<T>(obj);
}

bars 函数与您的 push_back 方法对应。

template <typename T>
void bar1 (T&& value) {
    process (move(value));   // you use move in your push_back method 
}

template <typename T>
void bar2 (T&& value) {
    process (forward<T>(value));
}

现在我们必须考虑4种情况:

[1] 传递 L 值,带正向的版本

Foo f(20);
bar2 (f);
cout << (f.i) << endl; // 20

[2] 传递 R 值,带正向的版本

Foo f(20);
bar2 (move(f));
cout << (f.i) << endl; // 0, it is OK bacuse we wanted to move 'f' object

[3] 传递 R 值,带移动的版本

Foo f(20);
bar1 (move(f));
cout << (f.i) << endl; // 0, ok, we wanted to move 'f' object

[4] 在你的 push_back 方法中传递 L 值,带有 move 的版本

Foo f(20);
bar1 (f);
cout << (f.i) << endl; // 0 !!! is it OK ? WRONG

在最后一种情况下,我们将 f 作为 L 值传递,但是这个对象在 bar1 函数中被移动,对我来说这是一种奇怪的行为并且是不正确的。

【讨论】:

  • 感谢您的回答。那么,在我的push_back 函数中,为什么我不能简单地使用new(data + size) T(std::move(value)); 而不是仅在调用push_back 时使用std::move
  • 您不能在 push_back 方法中使用 move,因为 move 总是返回 R 引用。 forward 的返回类型取决于 value 的类型。当您使用 L 值调用 push_back 时(您使用 .push_back(a) 传递 a 对象)您将获得 L-reference 作为 value 的类型,这没关系,因为您没有想要在 push_back 方法中移动 value 对象。当您使用 R 值调用 push_back 时(您使用 .push_back(move(a)) 传递 'a'),value 将具有 R 引用类型并且可以调用移动构造函数。
  • 很奇怪,我已经改变了这一点,即std::move(),之前我有std::forward&lt;U&gt;(),现在可以完美运行。我已经使用了一整天,没有任何明显的问题。
【解决方案2】:

在 C++ 中对对象执行 memmove 是不安全的。 你可以在这里找到更多信息Will memcpy or memmove cause problems copying classes?

如果这是 C++11 以后的版本,那么您要使用的是放置 new 和移动构造函数。 (除非您真的想自己继续分配内存,否则您可能只是将展示位置放入新的位置)

如果这是任何其他版本的 C++,那么您将不得不接受您将不得不复制对象(就像 stl 的其余部分一样)或者您的对象必须实现一个函数喜欢void moveTo(T&amp; other)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-31
    • 2012-05-15
    • 2015-04-23
    • 2018-10-20
    • 2010-11-14
    相关资源
    最近更新 更多