我是否应该复制std::函数,或者我可以总是引用它
Should I copy an std::function or can I always take a reference to it?
在我的c++应用程序中(使用Visual Studio 2010),我需要存储一个std::函数,如下所示:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
MyClass (Myfunction &myFunction);
private:
MyFunction m_myFunction; // Should I use this one?
MyFunction &m_myFunction; // Or should I use this one?
};
如您所见,我在构造函数中添加了函数实参作为引用。
但是,在我的类中存储函数的最好方法是什么?
- 我可以将函数存储为引用,因为std::function只是一个函数指针和函数的"可执行代码"保证留在内存中?
- 我是否必须在lambda传递和调用者返回的情况下复制?
我的直觉告诉我存储引用(甚至是const-reference)是安全的。我希望编译器在编译时为lambda生成代码,并在应用程序运行时将此可执行代码保存在"虚拟"内存中。因此,可执行代码永远不会被"删除",我可以安全地存储对它的引用。但这是真的吗?
我可以将函数存储为引用,因为std::function只是一个函数指针,函数的"可执行代码"保证留在内存中?
std::function
不仅仅是一个函数指针。它是任意可调用对象的包装器,并管理用于存储该对象的内存。与任何其他类型一样,只有当有其他方法可以保证所引用的对象在使用该引用时仍然有效时,才可以安全地存储引用。
除非你有一个很好的理由来存储引用,并且有一种方法来保证它仍然有效,否则按值存储它。
通过const
引用传递给构造函数是安全的,并且可能比传递值更有效。通过非const
引用传递是一个坏主意,因为它阻止您传递临时对象,因此用户不能直接传递lambda、bind
的结果或除std::function<int(int)>
本身之外的任何其他可调用对象。
如果您通过引用将函数传递给构造函数,而不创建它的副本,那么当函数超出该对象之外的作用域时,您将不走运,因为引用将不再有效。在前面的回答中已经说了很多了。
我想添加的是,相反,您可以通过值而不是引用将函数传递给构造函数。为什么?好吧,你无论如何都需要它的副本,所以如果你按值传递,编译器可以优化,当传入临时对象时(比如就地编写的lambda表达式)不需要复制。
当然,无论您做什么,当您将传入的函数赋值给变量时,您可能会进行另一次复制,因此使用std::move
来消除这种复制。例子:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
MyClass (Myfunction myFunction): m_myfunction(std::move(myFunction))
{}
private:
MyFunction m_myFunction;
};
因此,如果它们向上面传递右值,编译器将优化删除构造函数中的第一个副本,并且std::move删除第二个副本:)
如果(only)构造函数接受const引用,则将需要在函数中复制它,无论它是如何传入的。
另一种方法是定义两个构造函数,分别处理左值和右值:class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
//takes lvalue and copy constructs to local var:
MyClass (const Myfunction & myFunction): m_myfunction(myFunction)
{}
//takes rvalue and move constructs local var:
MyClass (MyFunction && myFunction): m_myFunction(std::move(myFunction))
{}
private:
MyFunction m_myFunction;
};
现在,您以不同的方式处理右值,并且通过显式处理它(而不是让编译器为您处理它)消除了在这种情况下复制的需要。可能比第一个更有效,但也需要更多的代码。
相关参考(可能在这里见过不少):http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
我建议你复制一份:
MyFunction m_myFunction; //prefferd and safe!
它是安全的,因为如果原始对象超出了作用域并破坏了它自己,那么它的副本仍然存在于类实例中。
随心所欲地复制。它是可复制的。标准库中的大多数算法都要求函子是。
然而,在一些重要的情况下,按引用传递可能会更快,所以我建议按常量引用传递,按值存储,这样你就不必关心生命周期管理了。所以:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
MyClass (const Myfunction &myFunction);
// ^^^^^ pass by CONSTANT reference.
private:
MyFunction m_myFunction; // Always store by value
};
通过传递常量或右值引用,可以向调用者保证在仍然可以调用该函数时不会修改该函数。这可以防止您错误地修改函数,通常应该避免故意这样做,因为它比使用返回值可读性差。
编辑:我最初在上面说"常量或右值",但戴夫的评论让我查了一下,确实右值引用不接受左值
作为一般规则(特别是如果您在一些高线程系统中使用这些规则),按值传递。实际上没有办法从线程内部验证底层对象是否仍然存在引用类型,因此您将自己暴露在非常讨厌的竞争和死锁错误中。
另一个需要考虑的问题是std::函数中的任何隐藏状态变量,对它们的修改不太可能是线程安全的。这意味着即使底层函数调用是线程安全的,std::function包装器的"()"调用也可能不是线程安全的。您可以通过始终使用std::函数的线程本地副本来恢复期望的行为,因为它们每个都有一个状态变量的独立副本。
- 函数向量_指针有不同的原型,我可以构建一个吗
- 我可以使用 g++ 进行三种比较 (<=>) 吗?
- 我可以使用条件运算符初始化C风格的字符串文字吗
- 我可以信任表示整数的浮点或双精度来保持精度吗
- 我可以将一个用clang c++11编译的对象与另一个用c++17编译的对象链接起来吗
- extern可以解决这个问题吗,或者我可以通过其他方法解决这个问题吗?
- 我可以在不声明变量类型的情况下获取输入,或者在 c++ 中为同一变量声明多个类型吗?
- 我可以在生成文件中为 CC、CFLAGS 使用其他名称,或者将它们硬连接到 GNU make 中
- 我可以让 std::list 按顺序插入新元素吗?或者必须使用std::sort
- 我可以将 std::数组转换为切片吗?或者我还能用什么
- 我可以通过 G++ 将 CUDA 与C++程序一起使用吗?或者 CUDA 只能使用 GCC 编译
- 这是openMP的正确用法吗?(或者:我可以信任默认设置吗?)
- 我必须创建一个对象来调用类方法吗?或者我可以只键入类名吗
- 我是否应该复制std::函数,或者我可以总是引用它
- 我是否应该保留随机分布对象实例,或者我可以总是重新创建它
- 最少的c++学习(或者我可以跳过的)
- 我可以(a)不通过输入迭代器写入,或者(b)只通过一个迭代器读取一次吗?
- 我可以声明一个未知类型的特征矩阵吗,或者至少声明一个泛型矩阵并稍后实例化吗
- 如何(或者我可以安全地,或者我可以)移动const对象
- 对于对象的映射,我可以放置对象,或者只是成对