当 pushing_back 到自定义容器时使用移动语义避免复制不会避免复制

Using move semantics to avoid copying when pushing_back to a custom container does not avoid copying

我已经实现了一个自定义容器(与 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,它会在创建、复制或移动其实例时打印消息,以帮助调试:

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 函数,它使用上述函数和 classes 运行测试用例来显示问题:

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的实例放入动态数组中,那么调用的只是移动构造函数,而不是复制构造函数。但是,如果我 push_back 将 yClass 实例转换为 Container 的实例,则仍在调用复制构造函数。

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

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

如果这是C++11以后的版本,那么你要使用的是placement new和move constructor。 (除非你真的想自己继续分配内存,否则你可能只是将新的位置装箱)

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

这样的函数

当你拨打这条线路时

myContainer.push_back(foo(a));

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

的信息

Returns an rvalue reference to arg if arg is not an lvalue reference.

If arg is an lvalue reference, the function returns arg without modifying its type.

在你的 push_back 中你调用

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

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

如果要移动a对象,可以这样写

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

编辑

你不应该在你的 push_back 函数中使用移动,下面是一个简单的例子。 假设你有这样的 class:

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

我们还有 2 个函数

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] pass L-value, version with forward

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

[2] pass R-value, version with forward

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 值,移动版本
Foo f(20);
bar1 (f);
cout << (f.i) << endl; // 0 !!! is it OK ? WRONG

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