首页 > 解决方案 > 右值引用,std::reference_wrappers 和 std::function

问题描述

我正在阅读 r 值引用和移动语义。不幸的是,用 std::function 和 std::reference_wrapper 进行实验让我更加困惑。

#include <iostream>
#include <string>
#include <string_view>
#include <functional>

class Greeting {
  std::string g;
  std::function <void(std::string_view)> f;
public:
  Greeting(std::string&& _g, std::function<void(std::string_view)>&& _f)
    : g(std::move(_g)), f(std::move(_f)){};
  void greet() {
    f(g);
  }
};

struct prefix_g {
  std::string g;
public:
  prefix_g(const std::string&& _g) : g(std::move(_g)) {}
  void operator() (std::string_view s) {
    std::cout <<g <<" "<< s << std::endl;
  }
};

int main() {
  prefix_g eng("Hello");

  Greeting g("World",eng);
  Greeting g2("World2",std::ref(eng)); // reference wrapper, special
                                       // forwarding for op ()
  std::string s3("world3"), s4("world3");

  // Greeting g3(std::ref(s3), std::ref(eng)); won't compile; &s3 -> &&s3
  // Greeting g3(s3, eng); won't compile lval to rval
  // Greeting g4(std::move(s4), std::move(eng)); // compiles, output Hello World2 -> World2 as g is moved?

  g.greet(); g2.greet();
  Greeting g4(std::move(s4), std::move(eng));
  g4.greet();

  Greeting g5("world5", std::move(eng)); // UB? move guarantees fn object is
                                         // still valid, ofc, g now gets default
                                         // init to empty
  g5.greet();
  return 0;
}
  1. 对 std::function 的 r 值引用实际上如何接受 l 值,例如。以防万一Greeting g("World",eng),任何其他参数都不能接受类似的左值(除了模板化构造函数并进行通用引用之外?)?
  2. 将 std::ref 传递给 std::function 时实际发生的情况,ref提到仅转发参数。但是,如果我在注释掉的 g4 显示时移动函数对象本身,我会看到 g2 的输出,它使用 std::ref 来实际看到移动的效果,只是打印 world2

  3. 移动后可调用对象会发生什么情况,字符串本身会移动,但函数仍然有效?(对于不同类型的函数对象,比如 struct f{void operator()() { //something }),这是否意味着 f 在移动后可能有效?)

标签: c++11c++17rvalue-referencestd-functionreference-wrapper

解决方案


那是因为您创建的所有对象实际上都不是 std::function,它们是可用于创建临时 std::function 的可调用对象。据我所知,最后一点(例如,我没有声称这是真的,我假设由于我的懒惰,见下文)是 UB,因为从对象移动可以保持任何有效状态,所以没有保证字符串成员实际上是空的。
根据经验,使用从对象中移动的方式不需要任何先决条件(重新分配,检查字符串 / vec 是否为空等符合条件)。
为了澄清,让我们看看这里的 std::function构造函数,有问题的构造函数是 (5):

template< class F >
function( F f ); 

因此,当您尝试使用可调用对象构造 std::function 时,默认情况下您将创建可调用对象的副本。例如,您可以通过使用 std::ref 来规避它,这将导致对可调用对象的后续更改反映在使用它的 std::function 中(因为您实际上是“从 ref”创建的,而不是像往常一样通过从复制创建)。


推荐阅读