假设我想编写一个调用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();
}
还是有更好的实现方法?
有关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);
}
答案 0 :(得分:26)
我将使用第一个(按值传递可调用项)。
如果呼叫者担心复制可调用对象的成本,则他们可以使用std::ref(f)
或std::cref(f)
使用reference_wrapper
传递它。
这样做,可以为呼叫者提供最大的灵活性。
答案 1 :(得分:10)
唯一的运行时成本
template<typename F>
void call100(F&& f) {
for (int i = 0; i < 100; ++i)
f();
}
是,如果您以多种方式传递f
,它可以具有更多版本(代码副本)。使用MSVC或带有ICF的黄金链接器,除非它们不同,否则这些副本仅花费编译时间,并且如果它们不同,则您可能希望保留它们。
template<typename F>
void call100(F f) {
for (int i = 0; i < 100; ++i)
f();
}
这是具有价值语义的优点;并遵循取值原则,除非您有充分理由不合理。 std::ref
/ std::cref
可以用一个持久引用来称呼它,对于c++17的prvalue,保证省略可以防止伪造的复制。
开个玩笑,您可以这样做:
template<typename F>
void call100(F&& f) {
for (int i = 0; i < 99; ++i)
f();
std::forward<F>(f)();
}
但这依赖于&&
上有operator()
个过载的人,没有人这样做。
答案 2 :(得分:8)
我认为没有明确的答案
第一个复制您传递的所有内容,这可能很昂贵 用于捕获lambda,但其他方式提供了最大的灵活性:
优点
缺点
第二个不能用于const对象。另一方面 它不复制任何内容,并允许可变对象:
优点
缺点
第三个不能用于可变的lambda,因此略有变化 修改第二个。
优点
缺点
除非复制,否则不能用const对象调用第四个对象 它们对lambda变得很尴尬。您也不能使用 它与预先存在的可变lambda对象而不复制它或 远离它(在过程中丢失),这是相似的 限制为1。
优点
缺点
就在那里。这里没有灵丹妙药,每个版本都有各自的优缺点。我倾向于将第一个作为默认值,但是对于某些类型的捕获lambda或更大的可调用对象,这可能会成为一个问题。并且您不能使用可变对象调用1)并获得预期的结果。如另一个答案中所述,其中一些可以通过std::ref
和其他处理实际T
类型的方式来克服。以我的经验,尽管T
可能与人们期望实现的目的(即副本等的可变性)有所不同,但是这些错误往往是相当讨厌的错误的来源。