首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如果我们有(N)RVO,那么移动构造函数实际上被调用了吗?

如果我们有(N)RVO,那么移动构造函数实际上被调用了吗?
EN

Stack Overflow用户
提问于 2018-04-24 17:16:19
回答 1查看 514关注 0票数 2

我从这里的几个问题中了解到,当一个对象被值返回时,(N)RVO阻止调用移动构造函数。典型例子:

代码语言:javascript
复制
struct Foo {
  Foo()            { std::cout << "Constructed\n"; }
  Foo(const Foo &) { std::cout << "Copy-constructed\n"; }
  Foo(Foo &&)      { std::cout << "Move-constructed\n"; }
  ~Foo()           { std::cout << "Destructed\n"; }
};

Foo makeFoo() {
  return Foo();
}

int main() { 
  Foo foo = makeFoo(); // Move-constructor would be called here without (N)RVO
}

启用(N)RVO的输出是:

代码语言:javascript
复制
Constructed
Destructed

那么,在什么情况下,移动构造函数将被调用,而不考虑(N)RVO的存在?你能举几个例子吗?换句话说:如果(N)RVO在默认情况下完成其优化工作,那么我为什么要关心实现移动构造函数呢?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-04-24 17:25:00

首先,您可能应该确保Foo遵循第三/五条规则并有移动/复制赋值操作符。以及移动构造函数和移动赋值操作符为这是很好的做法noexcept

代码语言:javascript
复制
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将所有权传递给接受参数的函数(按值或按rvalue-引用):

代码语言:javascript
复制
void takeFoo(Foo foo) {
  // use foo...
}

int main() { 
  Foo foo = makeFoo();

  // set data on foo...

  takeFoo(std::move(foo));
}

输出

代码语言:javascript
复制
Constructed
Move-constructed
Destructed
Destructed

用于标准库容器中。

移动构造函数的一个非常有用的例子是如果您有一个std::vector<Foo>。当您将push_back对象放入容器中时,它有时必须重新分配所有现有对象并将其移动到新内存中。如果Foo上有一个有效的移动构造函数可用,它将使用它而不是复制:

代码语言:javascript
复制
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());
}

输出

代码语言:javascript
复制
-- 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的值并将其移动到成员变量中:

代码语言:javascript
复制
class FooHolder {
  Foo foo_;
public:
  FooHolder(Foo foo) : foo_(std::move(foo)) {} 
};

int main() { 
  FooHolder fooHolder(makeFoo());
}

输出

代码语言:javascript
复制
Constructed
Move-constructed
Destructed
Destructed

这很好,因为它允许我定义一个构造函数,该构造函数接受lvalue或rvalue,而不需要不必要的副本。

击败NVRO的案例

RVO总是适用的,但也有击败NVRO的案例。例如,如果您有两个命名变量,并且在编译时不知道返回变量的选择:

代码语言:javascript
复制
Foo makeFoo(double value) {
  Foo f1;
  Foo f2;
  if (value > 0.5)
    return f1;
  return f2;
}

Foo foo = makeFoo(value);

输出

代码语言:javascript
复制
Constructed
Constructed
Move-constructed
Destructed
Destructed
Destructed

或者如果返回变量也是一个函数参数:

代码语言:javascript
复制
Foo appendToFoo(Foo foo) {

  // append to foo...

  return foo;
}

int main() { 
  Foo f1;
  Foo f2 = appendToFoo(f1);
}

输出

代码语言:javascript
复制
Constructed
Copy-constructed
Move-constructed
Destructed
Destructed
Destructed

优化rvalue的设置器

移动赋值操作符的一种情况是,如果您想为rvalue优化一个setter。假设您有一个包含FooHolderFoo,并且您需要一个setFoo成员函数。然后,如果您想对lvalue和rvalue进行优化,那么您应该有两个重载。一个引用到-const,另一个接受rvalue-引用:

代码语言:javascript
复制
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
}

输出

代码语言:javascript
复制
Constructed
Constructed
Copy-assigned  <-- setFoo with lvalue
Constructed
Move-assigned  <-- setFoo with rvalue
Destructed
Destructed
Destructed
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50007475

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档