문제

가상 기능이 인라인 될 필요가 없다는 코드 검토 의견을 받았을 때이 질문을 받았습니다.

인라인 가상 함수는 기능이 객체에서 직접 호출되는 시나리오에서 유용 할 수 있다고 생각했습니다. 그러나 반론이 내 마음에 나온 것입니다. 왜 가상을 정의하고 객체를 사용하여 메소드를 호출하고 싶습니까?

어쨌든 거의 확장되지 않았기 때문에 인라인 가상 함수를 사용하지 않는 것이 가장 좋습니까?

분석에 사용한 코드 스 니펫 :

class Temp
{
public:

    virtual ~Temp()
    {
    }
    virtual void myVirtualFunction() const
    {
        cout<<"Temp::myVirtualFunction"<<endl;
    }

};

class TempDerived : public Temp
{
public:

    void myVirtualFunction() const
    {
        cout<<"TempDerived::myVirtualFunction"<<endl;
    }

};

int main(void) 
{
    TempDerived aDerivedObj;
    //Compiler thinks it's safe to expand the virtual functions
    aDerivedObj.myVirtualFunction();

    //type of object Temp points to is always known;
    //does compiler still expand virtual functions?
    //I doubt compiler would be this much intelligent!
    Temp* pTemp = &aDerivedObj;
    pTemp->myVirtualFunction();

    return 0;
}
도움이 되었습니까?

해결책

가상 함수는 때때로 인쇄 될 수 있습니다. 우수한 발췌 C ++ FAQ:

"인라인 가상 호출을 인쇄 할 수있는 유일한 시간은 컴파일러가 가상 함수 호출의 대상 인 객체의"정확한 클래스 "를 알고있을 때입니다. 이것은 컴파일러에 포인터가 아닌 실제 객체가있는 경우에만 발생할 수 있습니다. 객체에 대한 참조. 즉, 로컬 객체, 글로벌/정적 객체 또는 복합재 내부에 완전히 포함 된 물체가있는 객체에 대한 참조. "

다른 팁

C ++ 11이 추가되었습니다 final. 이것은 허용 된 답변을 변경합니다. 더 이상 객체의 정확한 클래스를 알 필요가 없으며, 객체에 함수가 최종적으로 선언 된 클래스 유형이 있다는 것을 아는 것으로 충분합니다.

class A { 
  virtual void foo();
};
class B : public A {
  inline virtual void foo() final { } 
};
class C : public B
{
};

void bar(B const& b) {
  A const& a = b; // Allowed, every B is an A.
  a.foo(); // Call to B::foo() can be inlined, even if b is actually a class C.
}

가상 함수의 한 범주가 여전히 인라인 상태로 만드는 것이 합리적입니다. 다음 경우를 고려하십시오.

class Base {
public:
  inline virtual ~Base () { }
};

class Derived1 : public Base {
  inline virtual ~Derived1 () { } // Implicitly calls Base::~Base ();
};

class Derived2 : public Derived1 {
  inline virtual ~Derived2 () { } // Implicitly calls Derived1::~Derived1 ();
};

void foo (Base * base) {
  delete base;             // Virtual call
}

'기본'을 삭제하라는 호출은 가상 호출을 수행하여 올바른 파생 클래스 소멸자를 호출합니다.이 호출은 감소하지 않습니다. 그러나 각 파괴자가 호출을 호출하기 때문에 (이 경우 비어있는 경우) 컴파일러는 저것들 전화는 기본 클래스 기능을 사실상 호출하지 않기 때문에 전화합니다.

기본 클래스 생성자 또는 파생 구현이 기본 클래스 구현이라고하는 모든 기능 세트에 대해 동일한 원칙이 존재합니다.

비 인화 기능이없는 경우 V-Table을 방출하지 않는 컴파일러를 보았습니다 (헤더 대신 하나의 구현 파일에 정의 됨). 그들은 같은 오류를 던질 것입니다 missing vtable-for-class-A 또는 비슷한 것, 그리고 당신은 내가 있었던 것처럼 지옥처럼 혼란 스러울 것입니다.

실제로, 이는 표준에 맞지 않지만 이런 일이 발생하므로 컴파일러가 해당 장소에서 클래스에 대한 vtable을 방출 할 수 있도록 헤더에 적어도 하나의 가상 함수를 넣는 것을 고려하십시오. 나는 그것이 일부 버전에서 발생한다는 것을 알고 있습니다 gcc.

누군가 언급했듯이 인라인 가상 기능은 이점이 될 수 있습니다. 때때로,하지만 물론 가장 자주 당신은 할 때 사용할 것입니다. ~ 아니다 객체의 동적 유형을 알고 있습니다. 왜냐하면 그것이 모든 이유이기 때문입니다. virtual 처음에.

그러나 컴파일러는 완전히 무시할 수 없습니다 inline. 그것은 기능 전화 속도를 높이는 것 외에 다른 의미론을 가지고 있습니다. 그만큼 암시 적 인라인 클래스 내 정의는 정의를 헤더에 넣을 수있는 메커니즘입니다. inline 함수는 규칙을 위반하지 않고 전체 프로그램에서 여러 번 정의 할 수 있습니다. 결국, 당신은 서로 연결된 다른 파일에 헤더를 여러 번 포함 시켰지만 전체 프로그램에서 한 번만 정의했던 것처럼 동작합니다.

사실은 가상 함수는 항상 감소 할 수 있습니다, 그들은 정적으로 함께 연결되어 있다면 : 우리는 추상적 인 수업이 있다고 가정합니다. Base 가상 기능으로 F 파생 수업 Derived1 그리고 Derived2:

class Base {
  virtual void F() = 0;
};

class Derived1 : public Base {
  virtual void F();
};

class Derived2 : public Base {
  virtual void F();
};

hypotetical 전화 b->F(); (와 함께 b 유형의 Base*)는 분명히 가상입니다. 그러나 당신 (또는 컴파일러...) 그렇게 다시 작성할 수 있습니다 (가정하십시오 typeof a typeid-에서 사용할 수있는 값을 반환하는 기능 switch)

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // static, inlineable call
  case Derived2: b->Derived2::F(); break; // static, inlineable call
  case Base:     assert(!"pure virtual function call!");
  default:       b->F(); break; // virtual call (dyn-loaded code)
}

우리는 여전히 RTTI가 필요합니다 typeof, 호출은 기본적으로 vtable을 지침 스트림 내부에 포함시키고 모든 관련된 클래스에 대한 호출을 전문적으로 인쇄 할 수 있습니다. 이것은 몇 가지 수업 만 전문화함으로써 일반화 될 수 있습니다 (예 : Derived1):

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // hot path
  default:       b->F(); break; // default virtual call, cold path
}

가상 메소드를 인라인으로 표시하면 다음 두 가지 경우에서 가상 기능을 추가로 최적화하는 데 도움이됩니다.

인라인 실제로 아무것도하지 않습니다 - 그것은 힌트입니다. 컴파일러는이를 무시하거나 통화 이벤트를 인화 할 수 있습니다. 인라인 구현을 보고이 아이디어를 좋아한다면. 코드 선명도가 위험에 처한 경우 인라인 제거해야합니다.

상감 선언 된 가상 함수는 객체를 통해 호출 될 때 상감되고 포인터 또는 참조를 통해 호출 될 때 무시됩니다.

현대적인 컴파일러를 사용하면 상실에 해를 끼치 지 않습니다. 일부 고대 컴파일러/링커 콤보는 여러 VTables를 만들었을지 모르지만 더 이상 문제가되지 않습니다.

함수 호출이 모호하지 않고 함수가 인라인에 적합한 후보 인 경우, 컴파일러는 어쨌든 코드를 인화하기에 충분히 똑똑합니다.

나머지 시간 "인라인 가상"은 말도 안되며 실제로 일부 컴파일러는 해당 코드를 컴파일하지 않습니다.

컴파일러는 컴파일 시간에 호출을 명확하게 해결할 수있을 때 컴파일러가 함수 만 인라인 할 수 있습니다.

그러나 가상 함수는 런타임에 해결되므로 컴파일 유형에서는 동적 유형 (따라서 호출 될 기능 구현)을 결정할 수 없기 때문에 컴파일러는 호출을 인라인 할 수 없습니다.

가상 함수를 만들고 참조 나 포인터가 아닌 개체에서 호출하는 것이 합리적입니다. Scott Meyer는 자신의 저서 "Effection C ++"에서 상속되지 않은 비 약동적 기능을 결코 재정의하지 말 것을 권고합니다. 비 사건 기능을 갖춘 클래스를 만들고 파생 클래스에서 기능을 재정의 할 때 자신을 올바르게 사용해야 할 수도 있지만 다른 사람들이 올바르게 사용할 것이라고 확신 할 수는 없기 때문입니다. 또한, 나중에는 yoruself를 잘못 사용할 수 있습니다. 따라서 기본 클래스에서 함수를 만들고 확정 가능하기를 원한다면 가상으로 만들어야합니다. 가상 함수를 만들어 개체에서 호출하는 것이 합리적이라면, 그것들을 인화하는 것도 의미가 있습니다.

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