为什么移动向量和移动元素对向量的大小有不同的影响?
Why do moving vector and moving element have different effect on vector's size?
我知道 size()
和 empty()
不需要前置条件,因此可以在移出的对象上调用并且 return 正确的结果。但我不明白结果背后的理论。
std::vector<std::string> v = {"a", "b"};
std::string x = std::move(v[0]);
std::cout << x << std::endl; // a
std::cout << v.size() << std::endl; // 2
std::cout << v.empty() << std::endl; // 0, false
auto y = std::move(v);
std::cout << v.size() << std::endl; // 0
std::cout << v.empty(); // 1, true
结果显示,如果移动元素,向量的大小不会改变。但是如果你移动整个向量,它就会变成空的。有道理,但我觉得需要更多的解释,以便我以后处理类似的情况。
std::string x = std::move(v[0]);
您没有将元素移出集合(在本例中为矢量)。您正在将一个对象 - 一个 std::string
实例 - 保存在 v[0]
中移动到另一个对象 x
。集合本身没有改变。
auto y = std::move(v);
这会将 std::vector
对象 v
移动到 y
对象中。
当你移动一个对象时,你所做的就是说新项目将对包含的任何数据或指针负全部责任,而旧项目将有一个不会影响新项目的析构函数。对象有责任实现这个承诺。
当你移动vector中的对象时,vector并不知道对象被移动了;因此大小不会改变;但是 vector 持有的对象现在将是一个可以安全丢弃的 'blank' 项目 - 例如在 unique_ptr 的情况下,它将是一个指向 null_ptr 的 ptr .
移动向量做完全相同的事情 - 它将向量中的所有项目移动到新的项目 - 然后清除自身以确保在其析构函数上它不会影响它持有的项目。
好的,我会尽力解释,如果您发现任何错误,请原谅我。
首先我们要明白什么是std::move(...)
?
std::move(...)
接受一个对象和 returns 一个右值引用。而已。现在,当创建一个新对象时,我们可以使用该右值引用来调用实际位置的移动构造函数
移动操作发生。 (便宜的副本)。
让我们看一个例子
#include <iostream>
#include <cstring>
#include <cstdlib>
using std::cout;
struct foo
{
int *resource;
foo()
: resource{new int[10]}
{
cout << "c'tor called\n";
}
foo(const foo &arg)
:foo{} // delegating constructors. calls above constructor first then execute my body.
{
/* Here expensive copy happens */
memcpy(resource, arg.resource, 10);
cout << "copy c'tor called\n";
}
foo(foo &&arg)
: resource{arg.resource} // just change ownership of resource.
{
/*
Here actual move happens.
Its implementator's job.
*/
cout << "move c'tor called\n";
arg.resource = nullptr;
}
};
int main()
{
foo a{};
foo b{a}; // calls copy constructor
/*
Do some stuff with a and b
*/
foo c{std::move(a)} // calls move constructor
}
我在这里创建了 foo a
对象,我在其中使用新的内存块初始化资源。这里没什么特别的。
在第二行中,通过将对象 a
作为参数传递来创建新对象 b
。复制构造函数被调用,结果对象 b
与对象 a
.
相同
现在我想创建另一个应该与 a
相同的对象,同时我知道我不会再使用对象 a
,所以我 foo c{std::move(a)}
.
我也可以做到 foo c{a}
,但我知道我不会再使用 a
,所以交换对象的内容非常有效。
在移动构造函数中,我需要确保这样做 arg.resource = nullptr
。如果我不这样做,并且如果有人不小心更改了对象 a
,这也会间接影响对象 c
。
完成后对象 a
仍然有效并且存在。只是内容变了。现在来提问
std::string x = std::move(v[0]);
好的,通过调用移动构造函数创建新的字符串对象。
此操作后 v[0] 仍然存在,只是 v[0] 的内部内容发生了变化。所以 v.size() 是 2.
auto y = std::move(v);
在此操作之后,通过调用移动构造函数创建了新的向量对象 y
。
在 vector 的移动构造函数中,实现者必须做这样的事情
arg.container_size = 0;
因为 vector 的内容有了新的所有者。
我想自己回答这个问题,因为我认为一些图表可以帮助人们(包括我)更容易地理解这个问题。
一切都归结为 "moved"。假设我们有一个包含 3 个字符串的向量。
现在,如果我将整个向量移动到另一个向量,那么堆上的所有内容都归移入向量对象所有,从移出向量的角度来看,什么也看不到。
显然,向量的大小变为0
。
如果我移动一个元素,在这种情况下是一个字符串,那么只有字符串的数据会消失。向量的数据缓冲区未被触及。
矢量的大小仍然是 3
。
我知道 size()
和 empty()
不需要前置条件,因此可以在移出的对象上调用并且 return 正确的结果。但我不明白结果背后的理论。
std::vector<std::string> v = {"a", "b"};
std::string x = std::move(v[0]);
std::cout << x << std::endl; // a
std::cout << v.size() << std::endl; // 2
std::cout << v.empty() << std::endl; // 0, false
auto y = std::move(v);
std::cout << v.size() << std::endl; // 0
std::cout << v.empty(); // 1, true
结果显示,如果移动元素,向量的大小不会改变。但是如果你移动整个向量,它就会变成空的。有道理,但我觉得需要更多的解释,以便我以后处理类似的情况。
std::string x = std::move(v[0]);
您没有将元素移出集合(在本例中为矢量)。您正在将一个对象 - 一个 std::string
实例 - 保存在 v[0]
中移动到另一个对象 x
。集合本身没有改变。
auto y = std::move(v);
这会将 std::vector
对象 v
移动到 y
对象中。
当你移动一个对象时,你所做的就是说新项目将对包含的任何数据或指针负全部责任,而旧项目将有一个不会影响新项目的析构函数。对象有责任实现这个承诺。
当你移动vector中的对象时,vector并不知道对象被移动了;因此大小不会改变;但是 vector 持有的对象现在将是一个可以安全丢弃的 'blank' 项目 - 例如在 unique_ptr 的情况下,它将是一个指向 null_ptr 的 ptr .
移动向量做完全相同的事情 - 它将向量中的所有项目移动到新的项目 - 然后清除自身以确保在其析构函数上它不会影响它持有的项目。
好的,我会尽力解释,如果您发现任何错误,请原谅我。
首先我们要明白什么是std::move(...)
?
std::move(...)
接受一个对象和 returns 一个右值引用。而已。现在,当创建一个新对象时,我们可以使用该右值引用来调用实际位置的移动构造函数
移动操作发生。 (便宜的副本)。
让我们看一个例子
#include <iostream>
#include <cstring>
#include <cstdlib>
using std::cout;
struct foo
{
int *resource;
foo()
: resource{new int[10]}
{
cout << "c'tor called\n";
}
foo(const foo &arg)
:foo{} // delegating constructors. calls above constructor first then execute my body.
{
/* Here expensive copy happens */
memcpy(resource, arg.resource, 10);
cout << "copy c'tor called\n";
}
foo(foo &&arg)
: resource{arg.resource} // just change ownership of resource.
{
/*
Here actual move happens.
Its implementator's job.
*/
cout << "move c'tor called\n";
arg.resource = nullptr;
}
};
int main()
{
foo a{};
foo b{a}; // calls copy constructor
/*
Do some stuff with a and b
*/
foo c{std::move(a)} // calls move constructor
}
我在这里创建了 foo a
对象,我在其中使用新的内存块初始化资源。这里没什么特别的。
在第二行中,通过将对象 a
作为参数传递来创建新对象 b
。复制构造函数被调用,结果对象 b
与对象 a
.
现在我想创建另一个应该与 a
相同的对象,同时我知道我不会再使用对象 a
,所以我 foo c{std::move(a)}
.
我也可以做到 foo c{a}
,但我知道我不会再使用 a
,所以交换对象的内容非常有效。
在移动构造函数中,我需要确保这样做 arg.resource = nullptr
。如果我不这样做,并且如果有人不小心更改了对象 a
,这也会间接影响对象 c
。
完成后对象 a
仍然有效并且存在。只是内容变了。现在来提问
std::string x = std::move(v[0]);
好的,通过调用移动构造函数创建新的字符串对象。 此操作后 v[0] 仍然存在,只是 v[0] 的内部内容发生了变化。所以 v.size() 是 2.
auto y = std::move(v);
在此操作之后,通过调用移动构造函数创建了新的向量对象 y
。
在 vector 的移动构造函数中,实现者必须做这样的事情 arg.container_size = 0; 因为 vector 的内容有了新的所有者。
我想自己回答这个问题,因为我认为一些图表可以帮助人们(包括我)更容易地理解这个问题。
一切都归结为 "moved"。假设我们有一个包含 3 个字符串的向量。
现在,如果我将整个向量移动到另一个向量,那么堆上的所有内容都归移入向量对象所有,从移出向量的角度来看,什么也看不到。
显然,向量的大小变为0
。
如果我移动一个元素,在这种情况下是一个字符串,那么只有字符串的数据会消失。向量的数据缓冲区未被触及。
矢量的大小仍然是 3
。