C++ 传递lambda函数时有哪些折衷?

C++ 传递lambda函数时有哪些折衷?,c++,c++14,C++,C++14,我仍然没有很好地掌握完美的前进和前进参考。我试图理解在传递lambda表达式方面的差异。我的假设是,我会使用std::function或auto来接受lambda函数类型,但在查看示例时,我发现它们使用的是函数模板 我编写了一个小测试程序,试图理解左值和右值之间的差异,但我看不到任何差异。下面的SetLambda*()变体之间是否存在任何差异 据我所知,当给定左值时,唯一不起作用的是SetLambda5()。作为参考,我使用的是支持C++14的GCC版本 struct MyClass { t

我仍然没有很好地掌握完美的前进和前进参考。我试图理解在传递lambda表达式方面的差异。我的假设是,我会使用
std::function
auto
来接受lambda函数类型,但在查看示例时,我发现它们使用的是函数模板

我编写了一个小测试程序,试图理解左值和右值之间的差异,但我看不到任何差异。下面的
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;
}

当接受一个您将直接调用的未知函子而不存储它时,理想的、保留值类别的方法是:

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

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

在讨论lambdas和
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; }
};
使用该类型,下面是一个复制所有测试用例(加上一些变体)的示例:

我将逐一介绍,并解释他们的行为方式:

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


回到兰博达斯,一切都是一样的。只需将
ShowMe
替换为一些lambda类型,将
ShowMeHolder
替换为
std::function
。这两种类型都没有什么特别之处。lambda只是一个重载的
操作符()
,而
std::function
只是一个保存其他类似函数对象的对象(使用一系列技巧来存储任何类型的类似函数对象).

如果您将lambda存储在
std::function
中以供稍后调用,那么使用
std::function
将是最合适的。如果您要在不存储的情况下立即调用它,那么
SetLambda3
就是最好的选择(这是所有标准算法所使用的)。抱歉,在您回答问题时,我正在解决这个问题。我想你的意思是SetLambda7(),如果我稍后再调用它的话。愚蠢为什么使用函数模板?愚蠢,C++标准库和其他函数模板使用性能。如果您有一个函数模板,它接受一个可调用函数并只调用它,那么就没有间接寻址。编译器可以内联调用。实际上,您只是在注入代码,而没有
std::function
的低效性。这是否意味着如果我想同时执行这两种操作,我需要混合语法?ie模板void SetLambda(标准::函数
void StoreAFunctor(std::function<void()> func) {
    myFunctor = std::move(func);
}
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; }
};
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