2012-09-22 2 views
3

가상 함수 호출 최적화에 관한 질문이 있습니다. 어디 선가 읽고 (그리고 문제는 내가 지금 기사를 찾을 수있다)은 다음과 유사한 구조를 사용하여 V-테이블 룩업을 멀리 최적화 할 수있을 것을 :가상 함수 호출의 최적화

// Base.h 
class Base 
{ 
public: 
    virtual void Foo() = 0; 
}; 

// Concrete.h 
class Concrete : public Base 
{ 
public: 
    virtual void Foo() 
    { 
     // do something; 
    } 
}; 

//Some.h 
extern Base* const g_object; 

// Some.cpp 
Concrete on_stack_concrete; 

Base* const g_object = &on_stack_concrete; 

트릭을 (역동적이 아닌) 스택에 할당 된 변수에 대한 const 포인터를 사용하고 있어야하며, 컴파일러가이를 최적화하는 것이 가장 확실합니다. 그래서 사용자가 g_object-> Foo()를 호출 할 때마다 v-table look-up을 필요로하지 않고 무언가를 수행한다.

사실입니까?

미리 듣기에 감사드립니다.

EDIT :

이러한 구문의 사용 가능한 구체적인 구현의 인터페이스를 한정한다. 물론 "제한된"방법은 비공개로해야한다고 주장 할 수 있지만 때때로 라이브러리의 다른 모듈은 사용자가 객체를 조작 할 수 없도록 객체의 공용 추가 메서드에 액세스해야합니다. 그래서 # 정의를 사용하여 예를 들어, 하나의 유사한 코드를 생성 할 수 있습니다 :이 클래스의 선언 만 CPP 정의 할 수 있습니다 사실

// Some.cpp 
#ifdef _WIN32 
Win32Concrete concrete; 
#elif defined _UNIX 
UnixConcrete concrete; 
#endif 

Base* const g_global = &concrete; 

따라서 파일을 사용자가 자신의 실존을 인식하지 못합니다.

이유는이 처음에는 그러한 상수 포인터를 사용하는 것이 아니라 그러한 시나리오에서 v 테이블 검색을 최적화 할 수 있는지 여부입니다.

+2

아마도 컴파일러에 따라 다르므로 컴파일러에서 생성 한 어셈블리를 확인할 수 있어야합니다. – marcinj

+2

포인터가 항상 같은 개체를 가리키는 경우 포인터를 사용해야하는 이유는 무엇입니까? –

+0

@BoPersson, 나는 단지 하나의 좋은 응용 프로그램을 생각할 수 있습니다. 예를 들어 사용자에게 추상 인터페이스 만 노출하는 크로스 플랫폼 라이브러리와 일부 백엔드는 일부 #define에 따라 다양한 플랫폼에 대한 여러 구현을 사용합니다. 물론 각기 다른 정의를 가진 각 플랫폼에 대해 여러 컴파일을해야하지만 최종 사용자에게는 아무런 차이가 없습니다. –

답변

4

너는 오용하고있는 것 같다. virtual.

virtual은 런타임 다형성을 구현합니다. 그리고 당신이 묘사하는 시나리오는 그것을 사용하거나 필요로하지 않습니다. 어떤 컴파일 환경에서도 Win32ConcreteUnixConcrete이 존재하지는 않습니다.

// Some.cpp 
#ifdef _WIN32 
Win32Concrete concrete; 
#elif defined _UNIX 
UnixConcrete concrete; 
#endif 

Base* const g_global = &concrete; 

사용 : 대신

// CommonHeader.h 
#ifdef _WIN32 
typedef Win32Concrete Concrete; 
#elif defined _UNIX 
typedef UnixConcrete Concrete; 
#endif 

지금 당신의 기능을 가상 할 필요가 없습니다.

+0

이 방법으로 초기화 된 전역 변수의 포인트는 필요할 때마다 매개 변수로 함수에 전달하지 않으므로 이러한 시나리오는 발생하지 않습니다. 그것은 전 세계적으로 접근 할 수 있습니다. 여러분이 언급 한 "contex"는 제 이해에서 변하지 않습니다. –

+0

@AdrianLis 만약 당신이 전역 변수에 직접 접근한다면, 왜'Base * const g_object'를 생성할까요? 대신에'on_stack_concrete.foo()'를 사용할 수 있습니다. 그리고이 경우 검색을 피할 수 있습니다. –

+0

구체적인 클래스는 다른 (추가) 인터페이스를 사용자에게 노출시킬 수 있기 때문에 직접 사용할 수 없어야합니다. 그래서 필수 클래스에만 인터페이스를 제한하는 기본 클래스에 대한 포인터가 있습니다. 나는 가능한 사용법을 보여주기 위해 질문을 편집 할 것이다. –

1

가장 쉽게 해결할 수있는 방법은 Concrete 클래스의 제한된 메소드 친구에게 액세스해야하는 클래스를 만드는 것입니다. 그런 다음 클래스에 대한 전체 액세스 권한을 얻고 다른 모든 사람들은 공개 액세스 권한 만 얻습니다.

실용적이지 않은 경우 모든 제한된 메서드가 보호 된 기본 클래스에 구현을 배치 한 다음 보호 된 메서드를 노출하는 특수 클래스를 파생시킬 수 있습니다.

class Concrete 
{ 
public: 
    void foo() { ... } 
protected: 
    void bar() { ... } 
}; 

class ConcretePrivate : public Concrete 
{ 
public: 
    void bar() { Concrete:: bar(); } 
}; 

ConcretePrivate g_globalPrivate; 
Concrete& g_global = g_globalPrivate; 

g_global을 사용하는 코드는 구체적인 방법에만 액세스 할 수 있습니다. g_globalPrivate을 사용하는 코드는 ConcretePrivate 메서드에 액세스 할 수 있습니다.

1

이것은 네오/프레임 워크/FileSystem과 같은 둠 3 소스 코드 (https://github.com/id-Software/DOOM-3-BFG/)에서 사용 된 접근 방법입니다.http://fabiensanglard.net/doom3/

idTech4 높은 수준 :

idFileSystemLocal fileSystemLocal; 
idFileSystem *  fileSystem = &fileSystemLocal; 

그것에 대해 찾을 수있는 유일한 설명은 이것이다 :

extern idFileSystem *  fileSystem; 

그리고 신/프레임 워크/FileSystem.cpp이 정의 : 시간이 정의 객체는 가상 메소드가있는 모든 추상 클래스입니다. 일반적으로 각 가상 메소드 주소는 런타임에 호출하기 전에 vtable에서 조회해야하기 때문에 성능이 저하됩니다. 그러나이를 피하기위한 "속임수"가 있습니다.

데이터 세그먼트에 정적으로 할당 된 객체는 알려진 유형이므로 컴파일러는 commonLocal 메서드가 호출 될 때 vtable 조회를 최적화 할 수 있습니다.