假设我要编写一个调用null函数100次的函数。这些实现中哪一个最好,为什么?
template<typename F>
void call100(F f) {
for (int i = 0; i < 100; i++)
f();
}
template<typename F>
void call100(F& f) {
for (int i = 0; i < 100; i++)
f();
}
template<typename F>
void call100(const F& f) {
for (int i = 0; i < 100; i++)
f();
}
template<typename F>
void call100(F&& f) {
for (int i = 0; i < 100; i++)
f();
}
Run Code Online (Sandbox Code Playgroud)
还是有更好的实施方案?
关于4的更新
struct S {
S() {}
S(const S&) = delete;
void operator()() const {}
};
template<typename F>
void call100(F&& f) {
for (int i = 0; i < 100; i++)
f();
}
int main() {
const S s;
call100(s);
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*low 26
我将使用第一个(按值传递可调用对象)。
如果呼叫者担心复制可调用对象的成本,则可以使用std::ref(f)或std::cref(f)通过传递它reference_wrapper。
这样,您可以为呼叫者提供最大的灵活性。
Yak*_*ont 10
唯一的运行时成本
template<typename F>
void call100(F&& f) {
for (int i = 0; i < 100; ++i)
f();
}
Run Code Online (Sandbox Code Playgroud)
如果您f以多种方式传递,它可以具有更多版本(代码副本)。使用MSVC或带有ICF的黄金链接器,除非它们不同,否则这些副本仅花费编译时间,并且如果它们不同,则您可能希望保留它们。
template<typename F>
void call100(F f) {
for (int i = 0; i < 100; ++i)
f();
}
Run Code Online (Sandbox Code Playgroud)
这一优点是价值语义;并遵循取值原则,除非您有充分理由不合理。 std::ref/ std::cref让您使用持久引用来调用它,对于prvalues,保证c ++ 17的省略将防止虚假复制。
开个玩笑,您可以这样做:
template<typename F>
void call100(F&& f) {
for (int i = 0; i < 99; ++i)
f();
std::forward<F>(f)();
}
Run Code Online (Sandbox Code Playgroud)
但这取决于人们的负担&&过重operator(),而没人能做到。
我认为没有明确的答案:
第一个复制您传递的所有内容,对于捕获lambda来说可能很昂贵,但其他方式则提供了最大的灵活性:
优点
缺点
第二个不能用于const对象。另一方面,它不复制任何内容,并允许可变对象:
优点
缺点
第三个不能用于可变的lambda,因此对第二个做了一点修改。
优点
缺点
除非使用const对象复制,否则不能用const对象调用第四个对象,这对于lambdas变得很尴尬。您也不能将其与预先存在的可变lambda对象一起使用,除非对其进行复制或从中移动(在过程中丢失)(与1类似)。
优点
缺点
那里有它。这里没有灵丹妙药,每个版本都有各自的优缺点。我倾向于将第一个作为默认值,但是对于某些类型的捕获lambda或更大的可调用对象,这可能会成为一个问题。并且您不能使用可变对象调用1)并获得预期的结果。如另一个答案中所述,其中一些可以通过使用std::ref其他处理实际T类型的方法来克服。以我的经验,这些错误往往是相当讨厌的错误的源头,尽管T那时什么时候比人们期望的有所不同,即副本等的可变性。