传递右值与左值向量
passing a rvalue vs lvalue vector
我有一个方法my func
,它从get_vec
获取一个向量并将其传递给类A
的某个构造函数。
class A
{
public:
A(std::vector<int>&& vec) : m_vec(std::move(vec)) {}
std::vector<int> m_vec;
};
std::vector<int> get_vec()
{
std::vector<int> res;
// do something
return res;
}
void my_func()
{
std::vector<int> vec = get_vec();
A(std::move(vec));
}
https://godbolt.org/z/toQ5KZ
理想情况下,我希望向量被构造一次,但在这个例子中,我在get_vec
中创建向量,然后将其复制到my_func
,将其移动到构造函数,然后再次将其移动到A::m_vec
。
传递向量的正确和有效的方法是什么?
例如,如果您执行A a(get_vec());
,std::vector<int>&& m_vec
成员将导致悬空引用。
正确且安全的方法是按值赋予该成员:
std::vector<int> m_vec;
编译器优化通常仅在不更改可观察行为时才允许。然而,复制省略是少数几种允许以性能的名义更改程序输出(与抽象机器的输出相比)的情况之一。
我们可以用一个模拟向量来证明这一点,它的特殊成员函数有副作用(例如写入volatile
变量):
class MyVec
{
public:
MyVec() { x = 11; };
MyVec(const MyVec&) { x = 22; }
MyVec(MyVec&&) { x = 33; }
MyVec& operator=(const MyVec&) { x = 44; return *this; }
MyVec& operator=(MyVec&&) { x = 55; return *this; }
// Make this the same size as a std::vector
void* a = nullptr;
void* b = nullptr;
void* c = nullptr;
};
如果我们检查优化的程序集,我们可以看到只有一个默认构造函数和一个移动构造函数的副作用实际上保留在my_func
中,其他一切都被优化掉了。第一个默认构造函数是内联get_vec
,另一个是A
构造函数中的移动。这与您从临时构造成员时一样高效。
这是允许的,因为在从函数return
时,以及从(或多或少)临时初始化时,可以省略副本。后者曾经是"您可以在X x = getX();
中省略副本",但自 17 C++版本以来,从未创建过临时版本(https://en.cppreference.com/w/cpp/language/copy_initialization)。
我决定重写这个答案,因为豪尔赫·佩雷斯(Jorge Perez)善意地指出原始答案是有缺陷的,并且因为其他答案都没有真正完整地解决原始问题。
我首先编写了一个简单的测试程序:
#include <iostream>
class Moveable
{
public:
Moveable () { std::cout << "Constructorn"; }
~Moveable () { std::cout << "Destructorn"; }
Moveable (const Moveable&) { std::cout << "Copy constructorn"; }
Moveable& operator= (const Moveable&) { std::cout << "Copy assignmentn"; return *this; }
Moveable (const Moveable&&) { std::cout << "Move constructorn"; }
Moveable& operator= (const Moveable&&) { std::cout << "Move assignmentn"; return *this; }
};
class A
{
public:
A (Moveable &&m) : m_m (std::move (m)) {}
Moveable m_m;
};
Moveable get_m ()
{
Moveable res;
return res;
}
int main ()
{
Moveable m = get_m ();
A (std::move (m));
}
它给出了以下输出:
Constructor
Move constructor
Destructor
Destructor
因此,您可以立即看到代码中的低效率并不像您想象的那么糟糕 - 只有一个动作,没有副本。
现在,正如其他人所说:
Moveable m = get_m ();
不会因为指定返回值优化 (NRVO) 而复制任何内容。
而且只有一个动作,因为:
A (std::move (m));
实际上没有移动任何东西(它只是一个演员)。
现场演示
关于此举,可以说,如果你改变这一点,正在发生的事情会更清楚:
A (Moveable&& m) : m_m (std::move (m)) {}
对此:
A (Moveable& m) : m_m (std::move (m)) {}
因为您可以更改此设置:
A (std::move (m));
对此:
A {m};
并且仍然获得相同的输出(您需要大括号以避免"最烦人的解析")。
现场演示
- 写入向量<向量<bool>>
- 函数向量_指针有不同的原型,我可以构建一个吗
- std::向量与传递值的动态数组
- 将值指定给向量(2D)的向量中的某个位置
- 找不到成员对象:没有名为get_event()的成员,也处理多态性和向量
- 如何使用向量的template_back函数
- 尝试通过多个向量访问变量时,向量下标超出范围
- 如何通过派生类函数更改基类中的向量
- C++从另一个类访问公共静态向量的正确方法是什么
- 如何将ampl中的集合表示为c++中的向量
- 变量没有改变?通过向量的函数调用
- 迭代时从向量和内存中删除对象
- 向量 <int> a {N, 0} 和 int arr a[N] = {0} 的时间复杂度有什么区别
- 如何为模板化对象创建模板向量?VS正在投掷C3203
- 计算排序向量的向量中唯一值的计数
- 矩阵向量乘法(cublasDgemv)返回零
- 一对向量构造函数:初始值设定项列表与显式构造
- 将结构向量排序为子组
- 在C++中调整向量中的索引
- 向量元素的引用地址与它所指向的向量元素的地址不同.为什么