C ++ 11을 사용하는 중복 코드


80

현재 프로젝트를 진행 중이며 다음과 같은 문제가 있습니다.

두 가지 다른 방법으로 작업하려는 C ++ 메서드가 있습니다.

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

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

그리고 실제 기능이 훨씬 길기 때문에 내 코드를 복제하지 않고 싶습니다. 문제는 어떤 상황에서도 MyFunctionWithABonus 대신 MyFunction이 호출 될 때 프로그램에 실행 시간을 추가해서는 안된다는 것입니다. 그렇기 때문에 C ++ 비교로 확인하는 부울 매개 변수를 가질 수 없습니다.

내 아이디어는 C ++ 템플릿을 사용하여 내 코드를 가상으로 복제하는 것이었지만 추가 실행 시간이없고 코드를 복제 할 필요가없는 방법은 생각할 수 없습니다.

나는 템플릿 전문가가 아니기 때문에 뭔가 빠졌을 수 있습니다.

여러분 중 아이디어가 있습니까? 아니면 C ++ 11에서는 불가능합니까?


64
단순히 부울 검사를 추가 할 수 없는지 물어봐도 될까요? 거기에 많은 코드가있는 경우 간단한 부울 검사의 오버 헤드는 무시할 수 있습니다.
Joris

39
@plougue 분기 예측은 요즘 부울 검사를 실행하는 데 종종 0 프로세서 사이클이 소요되는 지점까지 매우 좋습니다.
Dan

4
@Dan과 동의합니다. 최근 분기 예측에는 특히 특정 분기에 여러 번 입력하는 경우 오버 헤드 가 거의 발생 하지 않습니다.
Akshay Arora

6
@Dan : 비교 및 ​​분기는 여전히 최대 하나의 매크로 융합 uop (최신 Intel 및 AMD x86 CPU에서)이며 0이 아닙니다. 코드의 병목 현상이 무엇인지에 따라이 uop를 디코딩 / 발행 / 실행하면 추가 ADD 명령과 같은 방식으로 다른 것에서주기를 훔칠 수 있습니다. 또한 부울 매개 변수를 전달하고 레지스터를 묶는 (또는 유출 / 다시로드해야 함) 명령의 수가 0이 아닙니다. 바라건대이 함수는 인라인으로 호출 및 인수 전달 오버 헤드가 매번 발생하지 않고 cmp + branch 일 수도 있지만 여전히
Peter Cordes

15
먼저 유지 보수가 쉬운 형식으로 코드를 작성 했습니까? 그런 다음 프로파일 러가 분기가 병목이라고 말했습니까? 이 사소한 결정에 소비하는 시간이 시간을 가장 잘 활용한다는 것을 제안하는 데이터가 있습니까?
GManNickG

답변:


55

템플릿과 람다를 사용하여 다음을 수행 할 수 있습니다.

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
내 이해에서 두 번째 솔루션에는 추가 함수 호출로 인해 추가 런타임이 있습니다. 이것이 첫 번째 경우입니까? 나는 확실하지 람다는이 경우에 작동하는 방법이야
plougue

10
정의가 보이면 컴파일러는 코드를 인라인하고 원래 코드에 대해 생성 된 것과 동일한 코드를 생성합니다.
Jarod42 2017-04-25

1
@Yakk 나는 그것이 특정 사용 사례와 "보너스 물건"이 누구의 책임인지에 달려 있다고 생각합니다. 종종 메인 알고리즘 사이에 bool 매개 변수, ifs 및 보너스 항목이 있으면 읽기가 더 어려워지고 "더 이상 존재하지 않음"으로 캡슐화되고 다른 곳에서 삽입되는 것을 선호합니다. 하지만 언제 전략 패턴을 사용하는 것이 적절한 지에 대한 질문은 아마도이 질문의 범위를 벗어난 것 같습니다.
Chris Drew

2
테일 호출 최적화는 일반적으로 재귀 사례를 최적화하려는 경우 중요합니다. 이 경우 플레인 인라이닝은 필요한 모든 것을 수행합니다.
Yakk-Adam Nevraumont 2017

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
그리고 그 검사는 잘 컴파일러에 의해 멀리 최적화 될 것이다
조나스

22
그리고 C ++ 17 if constexpr (bonus) { doBonusStuff(); } .
크리스 드류

5
@ChrisDrew 여기에 아무것도 추가하면 constexpr이 확실하지 않습니다. 그럴까요?
Gibet

13
@Gibet : doBonusStuff()보너스가 아닌 경우 어떤 이유로 든에 대한 호출이 컴파일되지 않으면 큰 차이를 만들 것입니다.
궤도의 경쾌함 경주

4
@WorldSEnder 예, enum 또는 enum 클래스가 constexpr (bonus == 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매크로를 사용하고 false이면 실패 할 수도 있습니다 (그러나 디버그 빌드에만 해당되며 릴리스 빌드는 확인을 수행하지 않습니다).

doBonusStuff()이러한 부작용은 릴리스 빌드에 나타나지 않으며 코드에서 가정 한 내용을 무효화 할 수 있으므로 부작용이있는 경우주의하십시오 .


부작용에 대한 경고는 좋지만 템플릿, if () {...}, constexpr 등 어떤 구조를 사용하든 마찬가지입니다.
pipe

OP 의견을 감안할 때 나는 이것이 그들에게 가장 적합한 솔루션이기 때문에 직접 찬성했습니다. 즉, 호기심 일뿐입니다. 왜 새로운 정의와 모든 문제가 발생하는지, doBonusStuff () 호출을 #if defined (NDEBUG) 안에 넣을 수 있습니까?
motoDrizzt

@motoDrizzt : OP가 다른 함수에서 이와 동일한 작업을 수행하려는 경우이 클리너 / 더 읽기 (및 쓰기)와 같은 새로운 매크로를 소개합니다. 일회성이라면 #if defined(NDEBUG)직접 사용 하는 것이 더 쉬울 것이라는 데 동의합니다 .
Cornstalks

@Cornstalks 예, 그것은 완전히 말이됩니다, 나는 그것에 대해 그렇게 생각하지 않았습니다. 그리고 난 아직도 :-)이 허용 대답해야한다 생각하고 있어요
motoDrizzt

18

호출자가 0 개 또는 1 개의 보너스 기능을 제공 할 수 있도록 가변 템플릿을 사용하는 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 2017

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{}); }

태그 디스 패칭은 널리 사용되는 일반 프로그래밍 기술이며 여기 에 기본에 대한 멋진 게시물이 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.