首先,您可能应该确保Foo
遵循三/五规则并具有移动/复制分配运算符。移动构造函数和移动赋值运算符是一个很好的做法noexcept
:
struct Foo {
Foo() { std::cout << "Constructed\n"; }
Foo(const Foo &) { std::cout << "Copy-constructed\n"; }
Foo& operator=(const Foo&) { std::cout << "Copy-assigned\n"; return *this; }
Foo(Foo &&) noexcept { std::cout << "Move-constructed\n"; }
Foo& operator=(Foo &&) noexcept { std::cout << "Move-assigned\n"; return *this; }
~Foo() { std::cout << "Destructed\n"; }
};
在大多数情况下,您可以遵循零规则并且实际上不需要定义任何这些特殊成员函数,编译器会为您创建它们,但它对此很有用。
(N)RVO 仅用于函数返回值。例如,它不适用于函数参数。当然,编译器可以在“as-if”规则下应用它喜欢的任何优化,所以我们在制作琐碎的例子时必须小心。
功能参数
在很多情况下会调用移动构造函数或移动赋值运算符。但一个简单的情况是,如果您使用std::move
将所有权转移到接受按值或右值引用参数的函数:
void takeFoo(Foo foo) {
// use foo...
}
int main() {
Foo foo = makeFoo();
// set data on foo...
takeFoo(std::move(foo));
}
输出:
Constructed
Move-constructed
Destructed
Destructed
用于标准库容器
移动构造函数的一个非常有用的例子是,如果你有一个std::vector<Foo>
. 当您将push_back
对象放入容器时,它有时必须重新分配所有现有对象并将其移动到新内存中。如果有可用的有效移动构造函数,Foo
它将使用它而不是复制:
int main() {
std::vector<Foo> v;
std::cout << "-- push_back 1 --\n";
v.push_back(makeFoo());
std::cout << "-- push_back 2 --\n";
v.push_back(makeFoo());
}
输出:
-- push_back 1 --
Constructed
Move-constructed <-- move new foo into container
Destructed
-- push_back 2 --
Constructed
Move-constructed <-- move existing foo to new memory
Move-constructed <-- move new foo into container
Destructed
Destructed
Destructed
Destructed
构造函数成员初始化器列表
我发现移动构造函数在构造函数成员初始值设定项列表中很有用。假设您有一个FooHolder
包含Foo
. 然后你可以定义一个构造函数,它接受一个Foo
值并将其移动到成员变量中:
class FooHolder {
Foo foo_;
public:
FooHolder(Foo foo) : foo_(std::move(foo)) {}
};
int main() {
FooHolder fooHolder(makeFoo());
}
输出:
Constructed
Move-constructed
Destructed
Destructed
这很好,因为它允许我定义一个构造函数,该构造函数接受左值或右值而不需要不必要的副本。
击败 NVRO 的案例
RVO 始终适用,但有些情况会击败 NVRO。例如,如果您有两个命名变量,并且在编译时不知道返回变量的选择:
Foo makeFoo(double value) {
Foo f1;
Foo f2;
if (value > 0.5)
return f1;
return f2;
}
Foo foo = makeFoo(value);
输出:
Constructed
Constructed
Move-constructed
Destructed
Destructed
Destructed
或者如果返回变量也是函数参数:
Foo appendToFoo(Foo foo) {
// append to foo...
return foo;
}
int main() {
Foo f1;
Foo f2 = appendToFoo(f1);
}
输出:
Constructed
Copy-constructed
Move-constructed
Destructed
Destructed
Destructed
优化右值的设置器
移动赋值运算符的一种情况是,如果您想优化右值的设置器。假设您有一个FooHolder
包含 a 的 aFoo
并且您想要一个setFoo
成员函数。然后,如果你想同时优化左值和右值,你应该有两个重载。一个采用对 const 的引用,另一个采用右值引用:
class FooHolder {
Foo foo_;
public:
void setFoo(const Foo& foo) { foo_ = foo; }
void setFoo(Foo&& foo) { foo_ = std::move(foo); }
};
int main() {
FooHolder fooHolder;
Foo f;
fooHolder.setFoo(f); // lvalue
fooHolder.setFoo(makeFoo()); // rvalue
}
输出:
Constructed
Constructed
Copy-assigned <-- setFoo with lvalue
Constructed
Move-assigned <-- setFoo with rvalue
Destructed
Destructed
Destructed