传递lambda函数的权衡是什么?

时间:2020-01-16 02:37:04

标签: c++ c++14

我仍然对完善的前进和前进参考没有很好的把握。我试图了解在传递lambda表达式方面的差异。我的假设是我将使用std::function<..>auto来接受lambda函数类型,但随后查看Folly source code,我发现它们正在使用函数模板。

我编写了一个小型测试程序来尝试理解左值和右值的区别,但看不到任何区别。下面的SetLambda*()变体之间有什么区别?

据我所知,给定左值时,唯一不起作用的是SetLambda5()。作为参考,我使用的是支持C ++ 14的GCC版本。

struct MyClass {
  template<typename Lambda>
  void SetLambda(Lambda&& lambda) { mLambda = std::forward<Lambda>(lambda); }

  template<typename Lambda>
  void SetLambda2(Lambda&& lambda) { mLambda = lambda; }

  template<typename Lambda>
  void SetLambda3(Lambda lambda) { mLambda = lambda; }

  void SetLambda4(auto lambda) { mLambda = lambda; }
  //void SetLambda5(auto& lambda) { mLambda = lambda; }
  void SetLambda6(auto&& lambda) { mLambda = lambda; }
  void SetLambda7(std::function<void()> lambda) { mLambda = lambda; }

  void Run() { mLambda(); }
  std::function<void()> mLambda;
};

int main() {
  auto lambda = []() { std::cout << "test0\n"; };

  MyClass myClass;
  myClass.SetLambda([]() { std::cout << "test1\n"; });
  myClass.Run();
  myClass.SetLambda(lambda);
  myClass.Run();
  myClass.SetLambda2([]() { std::cout << "test2\n"; });
  myClass.Run();
  myClass.SetLambda2(lambda);
  myClass.Run();
  myClass.SetLambda3([]() { std::cout << "test3\n"; });
  myClass.Run();
  myClass.SetLambda3(lambda);
  myClass.Run();
  myClass.SetLambda4([]() { std::cout << "test4\n"; });
  myClass.Run();
  myClass.SetLambda4(lambda);
  myClass.Run();
  //myClass.SetLambda5([]() { std::cout << "test5\n"; });
  //myClass.Run();
  //myClass.SetLambda5(lambda);
  //myClass.Run();
  myClass.SetLambda6([]() { std::cout << "test6\n"; });
  myClass.Run();
  myClass.SetLambda6(lambda);
  myClass.Run();
  myClass.SetLambda7([]() { std::cout << "test7\n"; });
  myClass.Run();
  myClass.SetLambda7(lambda);
  myClass.Run();

  return 0;
}

输出,以供参考:

test1
test0
test2
test0
test3
test0
test4
test0
test6
test0
test7
test0

1 个答案:

答案 0 :(得分:5)

在接受不需存储就直接调用的未知函子时,理想的保留值类别的方法是:

template <typename Func>
void DoAThing(Func&& func) {
    std::forward<Func>(func)(parameters);
}

当您要将函子存储在std::function对象中以供以后调用时,只需接受std::function并让隐式转换为您完成大部分工作:

void StoreAFunctor(std::function<void()> func) {
    myFunctor = std::move(func);
}

在进行更深入的解释之前,首先要提到的是,使用移动语义和完美转发的目的是避免执行昂贵的复制操作。我们希望尽可能地转移资源的所有权,而不是不必要地复制它们。如果您的对象不拥有任何可移动资源(例如没有捕获的lambda的情况),那么这都不重要。只需通过引用常量传递对象,然后根据需要将其复制。如果您的对象确实拥有一些可移动的资源,那么事情就会变得繁琐。

在讨论lambda和std::function之前,我将退后一步,看看这种简单的类型如何显示事情的动态:

struct ShowMe {
  ShowMe() { }
  ShowMe(const ShowMe&) { std::cout << "ShowMe copy constructed\n"; }
  ShowMe(ShowMe&&) { std::cout << "ShowMe move constructed\n"; }
  ShowMe& operator=(const ShowMe&) { std::cout << "ShowMe copy assigned\n"; return *this; }
  ShowMe& operator=(ShowMe&&) { std::cout << "ShowMe move assigned\n"; return *this; }
};

我还将使用这种简单类型作为std::function的替代品:

struct ShowMeHolder {
  ShowMeHolder() { }
  ShowMeHolder(const ShowMe& object) : mObject{object} { }
  ShowMeHolder(ShowMe&& object) : mObject{std::move(object)} { }
  ShowMeHolder& operator=(const ShowMe& object) { mObject = object; return *this; }
  ShowMeHolder& operator=(ShowMe&& object) { mObject = std::move(object); return *this; }

  ShowMe mObject;
};

使用该类型,这是一个示例,它再现了您所有的测试用例(以及一些变体):

struct MyClass {
  template<typename Object>
  void SetObject(Object&& object) { mObject = std::forward<Object>(object); }

  template<typename Object>
  void SetObject2(Object&& object) { mObject = object; }

  template<typename Object>
  void SetObject3(Object object) { mObject = object; }

  template <typename Object>
  void SetObject3Variant(Object object) { mObject = std::move(object); }

  void SetObject4(auto object) { mObject = object; }
  void SetObject4Variant(auto object) { mObject = std::move(object); }
  void SetObject5(auto& object) { mObject = object; }
  void SetObject6(auto&& object) { mObject = object; }
  void SetObject6Variant(auto&& object) { mObject = std::forward<decltype(object)>(object); }
  void SetObject7(ShowMeHolder object) { mObject = object; }
  void SetObject7Variant(ShowMeHolder object) { mObject = std::move(object); }

  ShowMeHolder mObject;
};

int main() {
  MyClass myClass;
  ShowMe object;

  std::cout << "SetObject move\n";
  myClass.SetObject(std::move(object));
  std::cout << "SetObject copy\n";
  myClass.SetObject(object);

  std::cout << "SetObject2 move\n";
  myClass.SetObject2(std::move(object));
  std::cout << "SetObject2 copy\n";
  myClass.SetObject2(object);

  std::cout << "SetObject3 move\n";
  myClass.SetObject3(std::move(object));
  std::cout << "SetObject3 copy\n";
  myClass.SetObject3(object);

  std::cout << "SetObject3Variant move\n";
  myClass.SetObject3Variant(std::move(object));
  std::cout << "SetObject3Variant copy\n";
  myClass.SetObject3Variant(object);

  std::cout << "SetObject4 move\n";
  myClass.SetObject4(std::move(object));
  std::cout << "SetObject4 copy\n";
  myClass.SetObject4(object);

  std::cout << "SetObject4Variant move\n";
  myClass.SetObject4Variant(std::move(object));
  std::cout << "SetObject4Variant copy\n";
  myClass.SetObject4Variant(object);

  //std::cout << "SetObject5 move\n";
  //myClass.SetObject5(std::move(object));
  std::cout << "SetObject5 copy\n";
  myClass.SetObject5(object);

  std::cout << "SetObject6 move\n";
  myClass.SetObject6(std::move(object));
  std::cout << "SetObject6 copy\n";
  myClass.SetObject6(object);

  std::cout << "SetObject6Variant move\n";
  myClass.SetObject6Variant(std::move(object));
  std::cout << "SetObject6Variant copy\n";
  myClass.SetObject6Variant(object);

  std::cout << "SetObject7 move\n";
  myClass.SetObject7(std::move(object));
  std::cout << "SetObject7 copy\n";
  myClass.SetObject7(object);

  std::cout << "SetObject7Variant move\n";
  myClass.SetObject7Variant(std::move(object));
  std::cout << "SetObject7Variant copy\n";
  myClass.SetObject7Variant(object);
}

这将提供以下输出:

SetObject move
ShowMe move assigned
SetObject copy
ShowMe copy assigned
SetObject2 move
ShowMe copy assigned
SetObject2 copy
ShowMe copy assigned
SetObject3 move
ShowMe move constructed
ShowMe copy assigned
SetObject3 copy
ShowMe copy constructed
ShowMe copy assigned
SetObject3Variant move
ShowMe move constructed
ShowMe move assigned
SetObject3Variant copy
ShowMe copy constructed
ShowMe move assigned
SetObject4 move
ShowMe move constructed
ShowMe copy assigned
SetObject4 copy
ShowMe copy constructed
ShowMe copy assigned
SetObject4Variant move
ShowMe move constructed
ShowMe move assigned
SetObject4Variant copy
ShowMe copy constructed
ShowMe move assigned
SetObject5 copy
ShowMe copy assigned
SetObject6 move
ShowMe copy assigned
SetObject6 copy
ShowMe copy assigned
SetObject6Variant move
ShowMe move assigned
SetObject6Variant copy
ShowMe copy assigned
SetObject7 move
ShowMe move constructed
ShowMe copy assigned
SetObject7 copy
ShowMe copy constructed
ShowMe copy assigned
SetObject7Variant move
ShowMe move constructed
ShowMe move assigned
SetObject7Variant copy
ShowMe copy constructed
ShowMe move assigned

Live Demo

我将仔细研究每个对象,并解释它们为何如此行事:

  • SetObject:此函数接受forwarding reference。与std::forward结合使用时,它们保留传递给它们的对象的值类别。这意味着当我们调用SetObject(object)object被复制分配,而当我们调用SetObject(std::move(object))object被移动分配。
  • SetObject2:此函数接受转发引用,但是由于您没有使用std::forward来保留参数的值类别,因此它始终是左值,因此是从中复制分配的。 / li>
  • SetObject3:此函数按值接受其参数。参数对象可以根据传递给函数的对象的值类别进行复制或移动构造,但是由于参数对象是左值,因此总是对其进行复制分配。
  • SetObject3Variant:与SetObject3一样,此函数按值接受其参数,并且根据传递给该函数的对象的值类别对参数对象进行复制或移动构造。然后,我们使用std::move将参数对象转换为一个右值,从而使其从中进行移动分配,而不是进行复制分配。
  • SetObject4:此功能的工作原理与SetObject3类似。 auto参数只是模板的语法糖。
  • SetObject4Variant:此功能的工作原理与SetObject3Variant
  • SetObject5:此函数通过对非常量的左值引用来接受其参数。那些只能绑定到左值,因此您根本不能将右值传递给它。由于它是一个左值,因此它的参数将被复制分配。
  • SetObject6:这与SetObject2完全一样。同样,auto参数只是模板的语法糖。
  • SetObject6Variant:与SetObject的工作原理完全相同,不同之处在于std::forward的语法有点奇怪,因为您没有要引用的显式模板类型参数。
  • SetObject7:此函数按值接受ShowMeHolder。将根据传递给对象的const ShowMe&对象的值类别,使用ShowMe&&ShowMe构造函数构造该对象。然后,该函数将ShowMeHolder参数对象复制分配给类成员,因为该参数是左值。
  • SetObject7Variant:此功能的作用类似于SetObject7,但由于使用std::move将参数对象强制转换为右值,因此该参数对象是从移动对象分配的。

将其放回lambda,一切工作都完全相同。只需将ShowMe替换为某种lambda类型,并将ShowMeHolder替换为std::function。这两种类型都没有什么特别的。 Lambda只是带有重载operator()的对象,而std::function只是一个包含其他类似函数的对象的对象(使用许多技巧可以存储 any 类功能对象的类型)。

相关问题