当 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
函数中移动,对我来说这是奇怪的行为并且是不正确的。
我已经实现了一个自定义容器(与 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
函数中移动,对我来说这是奇怪的行为并且是不正确的。