我有一个方法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));
}
理想情况下,我希望矢量构造一次,但在本例中,我在get_vec
中创建矢量,然后将其复制到my_func
,将其移动到构造函数,然后再将其移动到A::m_vec
。
传递向量的正确有效方法是什么?
通常只有在不改变可观察行为的情况下才允许编译器优化。但是,copy elision是少数情况之一,甚至可以在性能名称中更改程序的输出(与抽象机器的输出相比)。
我们可以用模拟向量证明这一点,它的特殊成员函数有副作用(比如写入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;
};
如果我们检查optimized assembly,我们可以看到只有一个默认构造函数和一个移动构造函数的副作用实际上保存在my_func
中,其他一切都被优化掉了。第一个默认构造函数是内联的get_vec
,另一个是A
的构造函数中的移动。从临时构造成员时,这可能是有效的。
这是允许的,因为当从函数return
ing时,以及从(或多或少)临时初始化时,可以省略副本。后者曾经是“你可以在X x = getX();
中删除副本”,但由于C ++ 17版本没有临时创建(https://en.cppreference.com/w/cpp/language/copy_initialization)。
例如,如果你做std::vector<int>&& m_vec
,A a(get_vec());
会员会引起悬挂参考。
正确和安全的方法是按值获得该成员:
std::vector<int> m_vec;
我决定改写这个答案,因为Jorge Perez善意地指出原件是有缺陷的,因为其他答案都没有真正解决原始问题。
我开始编写一个简单的测试程序:
#include <iostream>
class Moveable
{
public:
Moveable () { std::cout << "Constructor\n"; }
~Moveable () { std::cout << "Destructor\n"; }
Moveable (const Moveable&) { std::cout << "Copy constructor\n"; }
Moveable& operator= (const Moveable&) { std::cout << "Copy assignment\n"; return *this; }
Moveable (const Moveable&&) { std::cout << "Move constructor\n"; }
Moveable& operator= (const Moveable&&) { std::cout << "Move assignment\n"; 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 ();
由于Named Return Value Optimisation (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};
仍然得到相同的输出(你需要大括号,以避免'最令人烦恼的解析')。