使用移动语义来避免在push_back到自定义容器时进行复制不会避免复制

时间:2017-12-05 10:11:32

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

我已经实现了一个自定义容器(与std :: vector相同的静脉),我试图让它成为它的' push_back'函数会在移动语义上使用杠杆来避免创建任何被推回的副本 - 特别是当外部函数返回要推入容器的对象时。

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

以下是我的容器的简化版本:

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的实例直接放入动态数组中,则只调用move conmstructor,而不是复制构造函数。但是,如果我将yClass实例推送回Container的实例,则仍会调用复制构造函数。

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

2 个答案:

答案 0 :(得分:0)

在C ++中使用对象执行memmove是不安全的。 您可以在Will memcpy or memmove cause problems copying classes?

找到更多信息

如果这是C ++ 11以上,那么你想要使用的是placement new和move构造函数。 (除非你真的想继续自己分配内存,否则你可能只是把这个位置加入新内容)

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

这样的功能

答案 1 :(得分:0)

当你拨打这一行时

myContainer.push_back(foo(a));

L值传递到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作为L值传递,只能调用构造函数MyClass(const MyClass &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;
 }
};

我们还有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]传递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]传递L值,在push_back方法中带有move的版本

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

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