使用C ++ 11复制代码


80

我目前正在从事一个项目,但遇到以下问题。

我有一个C ++方法,希望以两种不同的方式工作:

void MyFunction()
{
  foo();
  bar();
  foobar();
}

void MyFunctionWithABonus()
{
  foo();
  bar();
  doBonusStuff();
  foobar();
}

而且我不想重复我的代码,因为实际功能要长得多。问题是在任何情况下都不能在调用MyFunction而不是MyFunctionWithABonus时增加程序的执行时间。这就是为什么我不能只具有通过C ++比较检查的布尔参数。

我的想法是使用C ++模板虚拟地复制我的代码,但是我无法想到一种无需额外执行时间且无需复制代码的方法。

我不是模板专家,所以我可能会缺少一些东西。

你们有什么主意吗?还是在C ++ 11中这是不可能的?


64
请问为什么不能简单地添加布尔检查?如果其中有很多代码,则简单布尔检查的开销可以忽略不计。
Joris

39
@plougue如今,分支预测非常好,以至于布尔检查通常需要0个处理器周期才能执行。
丹丹

4
同意@Dan。如今,分支预测的开销几乎为零,尤其是当您多次进入特定分支时。
Akshay Arora

6
@丹:比较分支仍然最多是一个宏融合的uop(在现代的Intel和AMD x86 CPU上),而不是零。取决于代码中的瓶颈,对该uop进行解码/发出/执行可能会从其他内容中窃取一个周期,这与额外的ADD指令所采用的方式相同。同样,仅传递布尔参数并使其绑定寄存器(或必须溢出/重载)是非零数量的指令。希望这个功能的内嵌因此呼叫和开销ARG传递是不存在的每一次,也许CMP +分支,但仍
彼得·柯德斯

15
您是否首先以易于维护的格式编写了代码?然后,您的分析器是否说分支是瓶颈?您是否有数据可以建议您在这个小小的决定上花费的时间,最好地利用您的时间?
GManNickG

Answers:


55

使用模板和lambda,您可以执行以下操作:

template <typename F>
void common(F f)
{
  foo();
  bar();
  f();
  foobar();
}

void MyFunction()
{
    common([](){});
}

void MyFunctionWithABonus()
{
  common(&doBonusStuff);
}

否则您就可以创建prefixsuffix运行。

void prefix()
{
  foo();
  bar();
}

void suffix()
{
    foobar();
}

void MyFunction()
{
    prefix();
    suffix();
}

void MyFunctionWithABonus()
{
    prefix();
    doBonusStuff();
    suffix();
}

12
我实际上更喜欢这两种解决方案,而不是布尔参数(模板或其他),而不管执行时间有何优势。我不喜欢布尔参数。
克里斯·德鲁

2
据我了解,第二个解决方案将由于附加的函数调用而具有附加的运行时。第一个是这种情况吗?我不确定lambda在这种情况下的工作方式
plougue

10
如果定义是可见的,则编译器可能会内联代码并生成与为原始代码生成的代码相同的代码。
Jarod42年

1
@Yakk我认为这将取决于特定的用例以及“额外的东西”是谁的责任。我经常发现在主要算法中包含布尔参数,ifs和Bonus东西使它更难阅读,并且希望它“不再存在”并被封装并从其他地方注入。但是我认为何时使用策略模式是适当的问题,可能超出此问题的范围。
克里斯·德鲁

2
当您要优化递归案例时,尾部调用优化通常很重要。在这种情况下,简单的内联...可以满足您的所有需求。
Yakk-Adam Nevraumont

128

这样的事情会做得很好:

template<bool bonus = false>
void MyFunction()
{
  foo();
  bar();
  if (bonus) { doBonusStuff(); }
  foobar();
}

通过以下方式调用:

MyFunction<true>();
MyFunction<false>();
MyFunction(); // Call myFunction with the false template by default

可以通过向函数添加一些漂亮的包装器来避免“丑陋”的模板:

void MyFunctionAlone() { MyFunction<false>(); }
void MyFunctionBonus() { MyFunction<true>(); }

您可以在那里找到有关该技术的一些不错的信息。那是一张“旧的”论文,但是该技术本身完全正确。

只要您可以使用一个不错的C ++ 17编译器,您甚至可以通过使用constexpr if来进一步推动该技术,例如:

template <int bonus>
auto MyFunction() {
  foo();
  bar();
  if      constexpr (bonus == 0) { doBonusStuff1(); }
  else if constexpr (bonus == 1) { doBonusStuff2(); }
  else if constexpr (bonus == 2) { doBonusStuff3(); }
  else if constexpr (bonus == 3) { doBonusStuff4(); }
  // Guarantee that this function will not compile
  // if a bonus different than 0,1,2,3 is passer
  else { static_assert(false);}, 
  foorbar();
}

11
编译器会很好地优化该检查
Jonas

21
在C ++ 17 if constexpr (bonus) { doBonusStuff(); }
克里斯·德鲁

5
@ChrisDrew不确定constexpr是否会在此处添加任何内容。会吗
Gibet

13
@Gibet:如果doBonusStuff()在非奖励情况下对的调用甚至由于某种原因而无法编译,那将会有很大的不同。
Lightness Races in Orbit

4
@WorldSEnder是的,如果是枚举或枚举类,则表示constexpr(奖励== MyBonus :: ExtraSpeed)。
Gibet

27

鉴于OP对调试提出的一些意见,以下是一个版本,它要求doBonusStuff()调试版本,但不要求发布版本(定义NDEBUG):

#if defined(NDEBUG)
#define DEBUG(x)
#else
#define DEBUG(x) x
#endif

void MyFunctionWithABonus()
{
  foo();
  bar();
  DEBUG(doBonusStuff());
  foobar();
}

如果希望检查条件,也可以使用assert宏;如果条件为假,则可以使用(但仅适用于调试版本;发布版本将不执行该检查)。

如果doBonusStuff()有副作用,请小心,因为这些副作用将不会在发行版本中出现,并且可能使代码中的假设无效。


关于副作用的警告是好的,但是无论使用哪种结构,无论是模板,if(){...},constexpr等,它都是正确的。–
pipe

鉴于OP的评论,我本人对此表示赞同,因为这正是他们的最佳解决方案。就是说,只是出于好奇:为什么只需将doBonusStuff()调用放在#if defined(NDEBUG)内,为什么新定义会带来所有复杂性?
motoDrizzt

@motoDrizzt:如果OP想要在其他功能中做同样的事情,我会发现引入了一个新的宏,例如更干净/更容易读取(和写入)的宏。如果这只是一次性的事情,那么我同意直接使用#if defined(NDEBUG)可能会更容易。
Cornstalks

@Cornstalks是的,这完全有道理,我没有那么想。而且我仍然认为这应该是公认的答案:-)
motoDrizzt

18

这是使用可变参数模板的Jarod42答案的细微变化,因此调用者可以提供零个或一个奖励功能:

void callBonus() {}

template<typename F>
void callBonus(F&& f) { f(); }

template <typename ...F>
void MyFunction(F&&... f)
{
  foo();
  bar();
  callBonus(std::forward<F>(f)...);
  foobar();
}

调用代码:

MyFunction();
MyFunction(&doBonusStuff);

11

另一个版本,仅使用模板而不使用重定向功能,因为您说过不需要任何运行时开销。就我而言,这只会增加编译时间:

#include <iostream>

using namespace std;

void foo() { cout << "foo\n"; };
void bar() { cout << "bar\n"; };
void bak() { cout << "bak\n"; };

template <bool = false>
void bonus() {};

template <>
void bonus<true>()
{
    cout << "Doing bonus\n";
};

template <bool withBonus = false>
void MyFunc()
{
    foo();
    bar();
    bonus<withBonus>();
    bak();
}

int main(int argc, const char* argv[])
{
    MyFunc();
    cout << "\n";
    MyFunc<true>();
}

output:
foo
bar
bak

foo
bar
Doing bonus
bak

现在只有一个版本的,MyFunc()其中该bool参数作为模板参数。


它不是通过调用bonus()增加编译时间吗?还是编译器检测到bonus <false>为空并且没有运行函数调用?
plougue

1
bonus<false>()调用bonus模板的默认版本(示例的第9行和第10行),因此没有函数调用。换句话说,MyFunc()编译为一个代码块(不包含任何条件),然后MyFunc<true>()编译为另一个代码块(不包含任何条件)。
David K

6
@plougue模板是隐式内联的,内联的空函数没有任何作用,可以由编译器消除。
Yakk-Adam Nevraumont

8

您可以使用标签分配和简单的函数重载:

struct Tag_EnableBonus {};
struct Tag_DisableBonus {};

void doBonusStuff(Tag_DisableBonus) {}

void doBonusStuff(Tag_EnableBonus)
{
    //Do bonus stuff here
}

template<class Tag> MyFunction(Tag bonus_tag)
{
   foo();
   bar();
   doBonusStuff(bonus_tag);
   foobar();
}

这很容易阅读/理解,可以不费吹灰之力地扩展(也不需要样板if子句-通过添加更多标签),并且当然不会留下任何运行时占用空间。

调用语法虽然非常友好,但是可以包装为普通调用:

void MyFunctionAlone() { MyFunction(Tag_DisableBonus{}); }
void MyFunctionBonus() { MyFunction(Tag_EnableBonus{}); }

标签分派是一种广泛使用的通用编程技术,是一篇有关基础知识的不错的文章。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.