是否可以在C++中移动临时对象的属性?
Can I move attributes of temporary objects in C++?
我正在编写一个依赖于向量中的项的迭代器和一个生成所述迭代器的迭代器工厂。该代码在概念上等同于以下内容:
struct Iter
{
int i = 0;
vector<int> vec;
Iter(const vector<int>& _vec):
vec(_vec)
{
cout << "copied vector of size "<<vec.size()<<" for iteratorn";
}
Iter(vector<int>&& _vec):
vec(move(_vec))
{
cout << "moved vector of size "<<vec.size()<<" for iteratorn";
}
int& operator*() { return vec[i]; }
Iter& operator++() { ++i; return *this; }
bool operator!=(const Iter& _it) const { return false; }
};
struct Factory
{
vector<int> fac_vec;
Factory(const vector<int>& _v):
fac_vec(_v)
{
cout << "copied vector of size " << fac_vec.size() << " for factoryn";
}
Factory(vector<int>&& _v):
fac_vec(move(_v))
{
cout << "moved vector of size "<<fac_vec.size()<<" for factoryn";
}
Iter begin() { return Iter(fac_vec); }
Iter end() { return Iter({}); }
};
int main(){
for(const int i: Factory({1,2,3}))
cout << i << "n";
return 0;
}
现在,运行这段代码给了我(g++ 8.3(:
moved vector of size 3 for factory [ initialization of the factory with {1,2,3} - moved ]
copied vector of size 3 for iterator [ initialization of the begin-iterator with fac_vec - copied ]
moved vector of size 0 for iterator [ initialization of the end-iterator with {} - moved ]
这有点令人失望,因为我希望最后一个begin()
会调用Iter
的移动构造函数,因为工厂在之后立即被摧毁(不是吗?(,并且编译器拥有决定这一点所需的所有信息。
我想我可以使用std::shared_ptr
做我想做的事情,但这会在代码和程序中产生开销。我宁愿告诉编译器在调用最后一个begin()
时移动fac_vec
。有没有办法做到这一点?
通常,在实现迭代器时,您需要获取向量的迭代器或对向量的引用:
struct Iter
{
int i = 0;
vector<int>* vec;
Iter(vector<int>& _vec):
vec(&_vec)
{
cout << "copied vector of size "<<vec->size()<<" for iteratorn";
}
int& operator*() { return (*vec)[i]; }
Iter& operator++() { ++i; return *this; }
bool operator!=(const Iter& _it) const { return false; }
};
这样你就不会复制。
更好的是,使用vector自己的迭代器:
struct Iter
{
vector<int>::iterator it;
Iter(vector<int>::iterator _it):
it(_it)
{ }
int& operator*() { return *it; }
Iter& operator++() { ++it; return *this; }
bool operator!=(const Iter& _it) const { return false; }
};
但是,您可以删除Iter
并简单地使用矢量迭代器:
struct Factory
{
// ...
auto begin() { return fac_vec.begin(); }
auto end() { return fac_vec.end(); }
};
现在,如果您确实想在迭代器中包含值(不推荐(。
编译器不会移动fac_vec
,因为它是向量的左值。这里没有动静。您需要向量的右值才能移动它。
您可以通过重载 rvalue 引用实例的函数来获得它:
struct Factory
{
vector<int> fac_vec;
Factory(const vector<int>& _v):
fac_vec(_v)
{
cout << "copied vector of size " << fac_vec.size() << " for factoryn";
}
Factory(vector<int>&& _v):
fac_vec(move(_v))
{
cout << "moved vector of size "<<fac_vec.size()<<" for factoryn";
}
// moving when Factory is temporary
Iter begin() && { return Iter(std::move(fac_vec)); }
Iter end() && { return Iter({}); }
// copying
Iter begin() const& { return Iter(fac_vec); }
Iter end() const& { return Iter({}); }
};
但是 for 循环不会调用移动版本。为什么?这将是危险的,因为这需要在类型上多次调用 move。
的范围大致等效于:
auto&& range = <range expr>; // forwarding reference to range (rvalue in your case)
auto begin = range.begin(); // range is lvalue, so calls the const&
auto end = range.end(); // range is lvalue, so calls the const&
for (/* ... */) {
// body
}
若要使用移动操作,需要多次将范围强制转换为右值,可能使用"移动自"值:
auto begin = std::move(range).begin(); // range is lvalue but moved so calls the &&
auto end = std::move(range).end(); // range is lvalue but moved so calls the &&
如果要在不推荐的迭代器中使用值,则不能使用范围 for 循环,并且必须使用旧样式的 for 循环。
请记住,像这样的调用通常被重新保护为代码气味,并且多次使用 move 更是如此。
相关文章:
- 在不复制临时对象的情况下延长其生存期
- 为什么当我们有常量引用时创建临时对象?
- 程序如何'remember'临时对象?
- 返回对临时对象的引用
- 防止临时对象文件访问 MSVC 中的磁盘
- 是否可以在C++中移动临时对象的属性?
- 通过引用传递临时对象
- 临时C++对象是否为左值?
- 临时对象:术语澄清
- 存储对(可能)临时对象的引用是否合法,只要引用不比对象存活?
- 临时对象有身份吗?
- 临时对象上的运算符重载
- 如何在没有 std::move 的情况下移动临时对象
- 临时对象在C++中是不可避免的吗?
- 类对象属性是否可以调用返回属性应具有的值的方法C++?
- 编译错误:临时对象构造函数中缺少参数
- 为什么在按值返回时创建临时对象,而不是在按值传递给函数参数时创建临时对象
- 我试图创建临时对象的方式有错误吗
- 子表达式中临时对象的生存期
- 对临时对象的Const引用不会延长其生存期