가상 기능을 인라인 할 필요가 없다는 코드 검토 의견을 받았을 때이 질문이 있습니다.
인라인 가상 함수는 함수가 객체에서 직접 호출되는 시나리오에서 유용 할 수 있다고 생각했습니다. 그러나 반론은 내 생각에왔다. 왜 가상을 정의하고 객체를 사용하여 메소드를 호출하고 싶을까?
인라인 가상 함수는 거의 확장되지 않으므로 사용하지 않는 것이 가장 좋습니까?
분석에 사용한 코드 스 니펫 :
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
과 파생 클래스 Derived1
와 Derived2
:
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
}
답변
가상 메소드를 인라인으로 표시하면 다음 두 가지 경우에 가상 기능을 더욱 최적화하는 데 도움이됩니다.
-
반복적으로 반복되는 템플릿 패턴 ( http://www.codeproject.com/Tips/537606/Cplusplus-Prefer-Curiously-Recurring-Template-Patt )
-
가상 메소드를 템플리트로 바꾸기 ( http://www.di.unipi.it/~nids/docs/templates_vs_inheritance.html )
답변
인라인은 실제로 아무것도하지 않습니다-힌트입니다. 컴파일러는이를 무시하거나 구현을보고이 아이디어를 좋아하는 경우 인라인 없이 호출 이벤트를 인라인 할 수 있습니다. 코드 선명도가 문제가되면 인라인 을 제거해야합니다.