인라인 가상 함수가 실제로 의미가 없습니까? 반론은 내 생각에왔다.

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

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

인라인 가상 함수는 거의 확장되지 않으므로 사용하지 않는 것이 가장 좋습니까?

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

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. 이것은 허용되는 답변을 변경합니다. 더 이상 객체의 정확한 클래스를 알 필요가 없습니다. 객체에 최소한 함수가 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
}

‘base’를 삭제하는 호출은 올바른 파생 클래스 소멸자를 호출하기위한 가상 호출을 수행하며이 호출은 인라인되지 않습니다. 그러나 각 소멸자가 부모 소멸자를 호출하므로 (이 경우 비어 있음) 컴파일러는 기본 클래스 함수를 가상으로 호출하지 않기 때문에 해당 호출을 인라인 할 수 있습니다 .

기본 클래스 생성자 또는 파생 구현이 기본 클래스 구현을 호출하는 함수 세트에도 동일한 원칙이 있습니다.


답변

인라인이 아닌 함수가 전혀 존재하지 않으면 (그리고 헤더 대신 하나의 구현 파일에 정의 된 경우) v-table을 방출하지 않는 컴파일러를 보았습니다. 그들은 missing vtable-for-class-A비슷한 또는 비슷한 것을 던질 것이고 , 당신은 내가했던 것처럼 지옥처럼 혼란 스러울 것입니다.

실제로, 그것은 표준을 준수하지 않지만, 헤더에 (가상 소멸자 인 경우에만) 적어도 하나의 가상 함수를 넣는 것을 고려하여 컴파일러가 해당 장소에서 클래스에 대한 vtable을 생성 할 수 있도록하십시오. 일부 버전에서 발생한다는 것을 알고 있습니다 gcc.

누군가가 언급 한 바와 같이, 인라인 가상 함수는 도움이 될 수 있습니다 가끔 있지만, 그렇게되면 물론 가장 자주 당신은 그것을 사용 하지 개체의 동적 유형을 알고이에 대한 모든 이유 때문에, virtual처음에이.

그러나 컴파일러는 완전히 무시할 수 없습니다 inline. 함수 호출 속도를 높이는 것 외에 다른 의미가 있습니다. 암시 인라인 수준의 정의에 대한 당신이 헤더에 정의를 넣을 수있는 메커니즘입니다 : 만 inline기능을 위반하는 어떤 규칙없이 전체 프로그램 전반에 걸쳐 여러 번 정의 할 수 있습니다. 결국 헤더를 여러 개의 서로 연결된 파일에 여러 번 포함하더라도 전체 프로그램에서 한 번만 정의한 것처럼 작동합니다.


답변

음, 사실 가상 함수가 항상 인라인 할 수 있습니다 그들은 정적으로 서로 연결되어있어 길이로, 우리는 추상 클래스가 있다고 가정 Base 가상 기능 F과 파생 클래스 Derived1Derived2:

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

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

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

가상 호출 b->F();( b유형이 있음 Base*)은 사실상 가상입니다. 그러나 당신 (또는 컴파일러 …)은 그렇게 다시 쓸 수 있습니다 (suppose typeof는에서 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
}

답변

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


답변

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