문제

다음과 같이 단순화할 수 있는 C++ 응용 프로그램이 있습니다.

class AbstractWidget {
 public:
  virtual ~AbstractWidget() {}
  virtual void foo() {}
  virtual void bar() {}
  // (other virtual methods)
};

class WidgetCollection {
 private:
  vector<AbstractWidget*> widgets;

 public:
  void addWidget(AbstractWidget* widget) {
    widgets.push_back(widget);
  }

  void fooAll() {
    for (unsigned int i = 0; i < widgets.size(); i++) {
      widgets[i]->foo();
    }
  }

  void barAll() {
    for (unsigned int i = 0; i < widgets.size(); i++) {
      widgets[i]->bar();
    }
  }

  // (other *All() methods)
};

내 애플리케이션은 성능이 매우 중요합니다.일반적으로 컬렉션에는 수천 개의 위젯이 있습니다.다음에서 파생된 클래스 AbstractWidget (그 중 수십 개가 있음) 일반적으로 많은 가상 기능을 재정의되지 않은 상태로 둡니다.재정의된 항목은 일반적으로 매우 빠르게 구현됩니다.

이를 고려하면 영리한 메타 프로그래밍을 통해 내 시스템을 최적화할 수 있다고 생각합니다.목표는 코드를 관리 가능하게 유지하면서 함수 인라인을 활용하고 가상 함수 호출을 방지하는 것입니다.이상하게 반복되는 템플릿 패턴에 대해 살펴보았습니다. 여기 설명을 위해).이것은 것 같다 거의 내가 원하는 대로 하세요. 하지만 정답은 아닙니다.

여기서 CRTP가 작동하도록 할 수 있는 방법이 있습니까?아니면 누구나 생각할 수 있는 다른 영리한 해결책이 있을까요?

도움이 되었습니까?

해결책

CRTP 또는 컴파일 타임 다형성은 컴파일 타임에 모든 유형을 알고 있는 경우에 사용됩니다.당신이 사용하는 한 addWidget 런타임에 위젯 목록을 수집하려면 fooAll 그리고 barAll 그런 다음 런타임에 동질적인 위젯 목록의 구성원을 처리해야 하며 런타임에 다양한 유형을 처리할 수 있어야 합니다.그래서 당신이 제시한 문제에 대해 런타임 다형성을 사용하는 데 어려움을 겪고 있다고 생각합니다.

물론 표준적인 대답은 런타임 다형성을 피하려고 시도하기 전에 런타임 다형성의 성능이 문제인지 확인하는 것입니다.

런타임 다형성을 정말로 피해야 하는 경우 다음 솔루션 중 하나가 작동할 수 있습니다.

옵션 1:컴파일 타임 위젯 컬렉션 사용

WidgetCollection의 멤버가 컴파일 타임에 알려지면 템플릿을 매우 쉽게 사용할 수 있습니다.

template<typename F>
void WidgetCollection(F functor)
{
  functor(widgetA);
  functor(widgetB);
  functor(widgetC);
}

// Make Foo a functor that's specialized as needed, then...

void FooAll()
{
  WidgetCollection(Foo);
}

옵션 2:런타임 다형성을 무료 함수로 대체

class AbstractWidget {
 public:
  virtual AbstractWidget() {}
  // (other virtual methods)
};

class WidgetCollection {
 private:
  vector<AbstractWidget*> defaultFooableWidgets;
  vector<AbstractWidget*> customFooableWidgets1;
  vector<AbstractWidget*> customFooableWidgets2;      

 public:
  void addWidget(AbstractWidget* widget) {
    // decide which FooableWidgets list to push widget onto
  }

  void fooAll() {
    for (unsigned int i = 0; i < defaultFooableWidgets.size(); i++) {
      defaultFoo(defaultFooableWidgets[i]);
    }
    for (unsigned int i = 0; i < customFooableWidgets1.size(); i++) {
      customFoo1(customFooableWidgets1[i]);
    }
    for (unsigned int i = 0; i < customFooableWidgets2.size(); i++) {
      customFoo2(customFooableWidgets2[i]);
    }
  }
};

추악하고 실제로는 OO가 아닙니다.템플릿은 모든 특수 사례를 나열할 필요성을 줄여 이를 수행하는 데 도움이 될 수 있습니다.다음과 같은 것을 시도해 보십시오(완전히 테스트되지 않음). 그러나 이 경우에는 인라인이 없는 상태로 돌아갑니다.

class AbstractWidget {
 public:
  virtual AbstractWidget() {}
};

class WidgetCollection {
 private:
  map<void(AbstractWidget*), vector<AbstractWidget*> > fooWidgets;

 public:
  template<typename T>
  void addWidget(T* widget) {
    fooWidgets[TemplateSpecializationFunctionGivingWhichFooToUse<widget>()].push_back(widget);
  }

  void fooAll() {
    for (map<void(AbstractWidget*), vector<AbstractWidget*> >::const_iterator i = fooWidgets.begin(); i != fooWidgets.end(); i++) {
      for (unsigned int j = 0; j < i->second.size(); j++) {
        (*i->first)(i->second[j]);
      }
    }
  }
};

옵션 3:OO를 제거하다

OO는 복잡성을 관리하는 데 도움이 되고 변화에 직면하여 안정성을 유지하는 데 도움이 되기 때문에 유용합니다.일반적으로 동작이 변경되지 않고 멤버 메소드가 매우 간단한 수천 개의 위젯과 같이 설명하는 상황에서는 관리하기가 복잡하거나 변경되지 않을 수 있습니다.그렇다면 OO가 필요 없을 수도 있습니다.

이 솔루션은 "가상" 메서드와 알려진 하위 클래스(OO가 아님)의 정적 목록을 유지해야 하고 가상 함수 호출을 인라인 함수에 대한 점프 테이블로 대체할 수 있다는 점을 제외하면 런타임 다형성과 동일합니다.

class AbstractWidget {
 public:
  enum WidgetType { CONCRETE_1, CONCRETE_2 };
  WidgetType type;
};

class WidgetCollection {
 private:
  vector<AbstractWidget*> mWidgets;

 public:
  void addWidget(AbstractWidget* widget) {
    widgets.push_back(widget);
  }

  void fooAll() {
    for (unsigned int i = 0; i < widgets.size(); i++) {
      switch(widgets[i]->type) {
        // insert handling (such as calls to inline free functions) here
      }
    }
  }
};

다른 팁

시뮬레이션 된 동적 바인딩 (CRTP의 다른 용도가 있음)은 기본 클래스 그 자체가 다형성이라고 생각하지만 클라이언트 실제로 하나의 특정 파생 수업에만 관심이 있습니다. 예를 들어, 일부 플랫폼 별 기능으로의 인터페이스를 나타내는 클래스가있을 수 있으며, 주어진 플랫폼에는 하나의 구현이 필요합니다. 패턴의 요점은 기본 클래스를 템플릿 화하여 여러 파생 클래스가 있더라도 기본 클래스는 컴파일 시간에 사용되는 시간을 알고 있습니다.

예를 들어 컨테이너가있을 때와 같은 런타임 다형성이 필요할 때 도움이되지 않습니다. AbstractWidget*, 각 요소는 여러 파생 클래스 중 하나 일 수 있으며, 반복해야합니다. CRTP (또는 템플릿 코드)에서 base<derived1> 그리고 base<derived2> 관련없는 클래스입니다. 그러므로도 마찬가지입니다 derived1 그리고 derived2. 다른 공통 기본 클래스가 없다면 그들 사이에는 동적 다형성이 없지만 가상 호출로 시작한 곳으로 돌아 왔습니다.

벡터를 여러 벡터로 교체하여 약간의 속도를 높일 수 있습니다. 나중에 새로운 파생 클래스를 추가하고 컨테이너를 업데이트하지 않을 때의 각각의 파생 클래스마다 하나씩, 하나는 일반적인 클래스에 대해 하나의 일반적인 클래스에 대해 속도를 높일 수 있습니다. 그런 다음 AddWidget이 일부 (느린) typeid 위젯을 확인하거나 가상 호출하여 위젯을 올바른 컨테이너에 추가하고 발신자가 런타임 클래스를 알고있을 때 과부하가있을 수 있습니다. 우연히 서브 클래스를 추가하지 않도록주의하십시오 WidgetIKnowAbout ~로 WidgetIKnowAbout* 벡터. fooAll 그리고 barAll 각 컨테이너 위로 반복 할 수 있습니다. fooImpl 그리고 barImpl 그런 다음 내려갈 기능. 그런 다음 희망적으로 훨씬 작게 반복됩니다 AbstractWidget* 벡터, 가상을 호출합니다 foo 또는 bar 기능.

그것은 약간 지저분하고 순수한 것이 아니지만 거의 모든 위젯이 컨테이너가 알고있는 클래스에 속하면 성능이 증가 할 수 있습니다.

대부분의 위젯이 컨테이너에 대해 알 수없는 클래스에 속하는 경우 (예를 들어 라이브러리가 다른 라이브러리에 있기 때문에), 동적 링커가 인라인 할 수 없다면 내릴 수는 없습니다. 광산은 할 수 없습니다). 멤버 함수 포인터를 혼란스럽게하여 가상 호출 오버 헤드를 떨어 뜨릴 수 있지만, 게인은 거의 확실하거나 부정적 일 것입니다. 가상 호출의 대부분의 오버 헤드는 가상 조회가 아니라 호출 자체에 있으며 기능 포인터를 통한 호출은 인식되지 않습니다.

다른 방법으로보십시오 : 코드를 인쇄 해야하는 경우 실제 기계 코드가 다른 유형에 따라 달라야 함을 의미합니다. 즉, 컬렉션에서 철수 된 일부 포인터 유형에 따라 기계 코드가 루프를 통해 각 패스에서 ROM에서 명확하게 변경할 수 없기 때문에 여러 루프 또는 스위치가있는 루프가 필요하다는 것을 의미합니다.

글쎄, 나는 객체에 루프가 RAM으로 복사하고, 실행 가능 및 점프하는 ASM 코드가 포함될 수 있다고 생각합니다. 그러나 그것은 C ++ 멤버 기능이 아닙니다. 그리고 그것은 휴대 할 수 없습니다. 그리고 아마도 빠르지 않을 것입니다. 복사와 ICACHE 무효화와 함께. 그래서 가상 호출이 존재하는 이유입니다 ...

짧은 대답은 아니오입니다.

긴 대답 (또는 여전히 다른 답변에 대한 짧은 캠프장 :-)

런타임에 실행할 기능을 파악하려고 노력하고 있습니다 (이것이 가상 함수가 무엇인지). 벡터가있는 경우 (컴파일 타임에 회원을 결정할 수없는 경우) 시도하는 내용에 관계없이 기능을 인화하는 방법을 해결할 수 없습니다.

이에 대한 유일한 것은 벡터에 항상 동일한 요소를 포함하는 경우 (즉, 런타임에 실행될 내용을 컴파일 시간을 해결할 수 있다는 것입니다). 그런 다음 이것을 재 작업 할 수 있지만 벡터가 아닌 다른 일이 요소를 고정해야합니다 (아마도 모든 요소가 멤버로 구성된 구조).

또한 가상 파견이 병목 현상이라고 생각하십니까?
개인적으로 나는 그것을 의심합니다.

당신이 여기에있을 문제는 다음과 같습니다 WidgetCollection::widgets. 벡터는 한 유형의 항목 만 포함 할 수 있으며 CRTP를 사용하려면 각각 AbstractWidget 원하는 파생 유형으로 템플릿 한 다른 유형을 갖습니다. 즉, 당신은 AbstractWidget 다음과 같이 보일 것입니다.

template< class Derived >
class AbstractWidget {
    ...
    void foo() {
        static_cast< Derived* >( this )->foo_impl();
    }        
    ...
}

이는 각각을 의미합니다 AbstractWidget 다른 Derived 유형은 다른 유형을 구성합니다 AbstractWidget< Derived >. 이것을 단일 벡터에 저장하는 것은 작동하지 않습니다. 따라서이 경우 가상 기능이 갈 길인 것 같습니다.

벡터가 필요하다면 아닙니다. STL 컨테이너는 완전히 균질하므로 위트가와 위젯을 동일한 컨테이너에 보관 해야하는 경우 일반적인 부모로부터 상속되어야합니다. 그리고 Widgeta :: bar ()가 Widgetb :: bar ()와 다른 것을 수행하면 함수를 가상으로 만들어야합니다.

모든 위젯이 같은 컨테이너에 있어야합니까? 당신은 같은 일을 할 수 있습니다

vector<widgetA> widget_a_collection;
vector<widgetB> widget_b_collection;

그러면 기능이 가상 일 필요는 없습니다.

모든 노력을 겪은 후에는 성능 차이가 없을 것입니다.

이것은 절대적으로입니다 잘못된 최적화하는 방법. 임의의 코드 라인을 변경하여 로직 버그를 고치지 않겠습니까? 아뇨, 어리석은 일이야 실제로 문제를 일으키는 줄을 먼저 찾을 때까지 코드를 "수정"하지 않습니다. 그래서 왜 당신을 대할 것인가 성능 버그가 다르게?

응용 프로그램을 프로필하고 실제 병목 현상이 어디에 있는지 찾아야합니다. 그런 다음 해당 코드 속도를 높이고 프로파일 러를 다시 실행하십시오. 성능 버그 (너무 느리게 실행)가 사라질 때까지 반복하십시오.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top