final
함수에 대한 C ++ 11 키워드 의 목적은 무엇입니까 ? 파생 클래스가 함수를 재정의하는 것을 방지한다는 것을 알고 있지만 이것이 사실이라면 비가 상 final
함수 로 선언하기에 충분하지 않습니까? 내가 여기서 놓친 또 다른 것이 있습니까?
final
함수에 대한 C ++ 11 키워드 의 목적은 무엇입니까 ? 파생 클래스가 함수를 재정의하는 것을 방지한다는 것을 알고 있지만 이것이 사실이라면 비가 상 final
함수 로 선언하기에 충분하지 않습니까? 내가 여기서 놓친 또 다른 것이 있습니까?
답변:
idljarn이 이미 주석에서 언급했듯이 누락 된 것은 기본 클래스에서 함수를 재정의 하는 경우 비가 상으로 표시 할 수 없다는 것입니다.
struct base {
virtual void f();
};
struct derived : base {
void f() final; // virtual as it overrides base::f
};
struct mostderived : derived {
//void f(); // error: cannot override!
};
virtual
C ++ 11은 오류를 유발할 수 있다는 사실을 잊어 버리고 C ++ 11 override
은 해당 상황을 감지하고 실제로 재정의 하려는 함수가 실제로 숨길
클래스가 상속되지 않도록하는 것입니다. 에서 위키 백과 :
C ++ 11은 클래스에서 상속받지 못하거나 파생 클래스에서 메서드를 재정의하는 것을 단순히 방지하는 기능도 추가합니다. 이것은 특수 식별자 final로 수행됩니다. 예를 들면 다음과 같습니다.
struct Base1 final { }; struct Derived1 : Base1 { }; // ill-formed because the class Base1 // has been marked final
또한 파생 클래스에서 가상 함수가 재정의되지 않도록 가상 함수를 표시하는 데 사용됩니다.
struct Base2 { virtual void f() final; }; struct Derived2 : Base2 { void f(); // ill-formed because the virtual function Base2::f has // been marked final };
Wikipedia는 또한 흥미로운 지적을합니다 .
언어 키워드 도
override
아닙니다final
. 그것들은 기술적으로 식별자입니다. 그들은 특정 상황에서 사용될 때만 특별한 의미를 얻습니다 . 다른 위치에서는 유효한 식별자가 될 수 있습니다.
즉, 다음이 허용됩니다.
int const final = 0; // ok
int const override = 1; // ok
"최종"은 또한 컴파일러 최적화가 간접 호출을 우회 할 수있게합니다.
class IAbstract
{
public:
virtual void DoSomething() = 0;
};
class CDerived : public IAbstract
{
void DoSomething() final { m_x = 1 ; }
void Blah( void ) { DoSomething(); }
};
"final"을 사용하면 컴파일러는 에서 또는 인라인 CDerived::DoSomething()
에서 직접 호출 할 수 있습니다 Blah()
. 그것 없이는 재정의 된 파생 클래스 내에서 호출 될 수 Blah()
있기 때문에 내부에서 간접 호출을 생성해야합니다 .Blah()
DoSomething()
"최종"의 시맨틱 한 측면에 추가 할 것은 없습니다.
그러나 "최종"이 그리 멀지 않은 미래에 매우 중요한 컴파일러 최적화 기술 이 될 수 있다고 chris green의 의견에 덧붙이고 싶습니다 . 그가 언급 한 간단한 경우뿐만 아니라 "최종"에 의해 "폐쇄"될 수있는보다 복잡한 실제 클래스 계층 구조에 대해서도 컴파일러는 일반적인 vtable 방식보다 더 효율적인 디스패치 코드를 생성 할 수 있습니다.
vtables의 한 가지 주요 단점은 이러한 가상 객체 (일반적인 Intel CPU에서 64 비트로 가정)에 대해 포인터 만 캐시 라인의 25 % (64 바이트 중 8 바이트)를 소비한다는 것입니다. 내가 작성하는 응용 프로그램의 종류에서 이것은 매우 심하게 아프다. (그리고 내 경험상 그것은 순수한 프로그래머 관점에서, 즉 C 프로그래머에 의한 C ++에 대한 # 1 논쟁입니다.)
C ++에서는 그리 드문 일이 아닌 극단적 인 성능이 필요한 응용 프로그램에서는 실제로 C 스타일이나 이상한 템플릿 저글링 에서이 문제를 수동으로 해결할 필요가 없으므로 대단해질 수 있습니다.
이 기술을 가상화 해제라고 합니다. 기억할 가치가있는 용어. :-)
Andrei Alexandrescu의 최근 연설이 있는데, 오늘날 이러한 상황을 해결할 수있는 방법과 "최종"이 미래에 유사한 사례를 "자동으로"해결하는 방법에 대해 잘 설명하고 있습니다 (청중과 논의).
http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly
비가 상 기능에는 Final을 적용 할 수 없습니다.
error: only virtual member functions can be marked 'final'
비가 상 방법을 '최종'으로 표시 할 수 있다는 것은 의미가 없습니다. 주어진
struct A { void foo(); };
struct B : public A { void foo(); };
A * a = new B;
a -> foo(); // this will call A :: foo anyway, regardless of whether there is a B::foo
a->foo()
항상 전화 A::foo
합니다.
그러나 A :: foo가 virtual
이면 B :: foo가이를 대체합니다. 이것은 바람직하지 않을 수 있으므로 가상 기능을 최종적으로 만드는 것이 합리적입니다.
문제는 가상 함수에 final을 허용하는 이유 입니다. 계층 구조가 깊은 경우 :
struct A { virtual void foo(); };
struct B : public A { virtual void foo(); };
struct C : public B { virtual void foo() final; };
struct D : public C { /* cannot override foo */ };
그런 다음 final
얼마나 많은 재정의를 수행 할 수 있는지에 대한 '바닥'을 넣습니다. 다른 클래스는 A와 B를 확장 foo
하고 해당 클래스를 재정의 할 수 있지만 클래스가 C를 확장하면 허용되지 않습니다.
따라서 'top-level'foo를 만드는 것은 의미가 없지만 아마도 final
더 낮을 수도 있습니다.
(그러나 최종 단어를 확장하고 비가 상 구성원에게 재정의 할 여지가 있다고 생각합니다.하지만 다른 의미를 가질 것입니다.)
final
. 당신이 알고있는 경우 예를 들어, 모든 원하는 Shape
에의 foo()
더 파생 된 형태는 수정하지 것을 미리 정의 및 명확한 - 비슷한는. 아니면 내가 틀렸고 그 경우에 더 나은 패턴을 사용합니까? 편집 : 아, 아마도 그 경우에, 최상위 레벨 foo()
virtual
로 시작 해서는 안되기 때문에 ? 그러나 여전히 (다형성으로) 호출 되어도 숨길 수 있습니다 Shape*
.
내가 좋아하는 '최종'키워드의 사용 사례는 다음과 같습니다.
// This pure abstract interface creates a way
// for unit test suites to stub-out Foo objects
class FooInterface
{
public:
virtual void DoSomething() = 0;
private:
virtual void DoSomethingImpl() = 0;
};
// Implement Non-Virtual Interface Pattern in FooBase using final
// (Alternatively implement the Template Pattern in FooBase using final)
class FooBase : public FooInterface
{
public:
virtual void DoSomething() final { DoFirst(); DoSomethingImpl(); DoLast(); }
private:
virtual void DoSomethingImpl() { /* left for derived classes to customize */ }
void DoFirst(); // no derived customization allowed here
void DoLast(); // no derived customization allowed here either
};
// Feel secure knowing that unit test suites can stub you out at the FooInterface level
// if necessary
// Feel doubly secure knowing that your children cannot violate your Template Pattern
// When DoSomething is called from a FooBase * you know without a doubt that
// DoFirst will execute before DoSomethingImpl, and DoLast will execute after.
class FooDerived : public FooBase
{
private:
virtual void DoSomethingImpl() {/* customize DoSomething at this location */}
};
final
함수를 재정의하지 않으려는 명확한 의도를 추가하고이를 위반하면 컴파일러 오류가 발생합니다.
struct A {
virtual int foo(); // #1
};
struct B : A {
int foo();
};
코드가 의미하는대로 컴파일하고 B::foo
재정의 A::foo
합니다. B::foo
그건 그렇고 가상입니다. 그러나 # 1을로 변경하면 virtual int foo() final
이는 컴파일러 오류이므로 A::foo
파생 클래스에서 더 이상 재정의 할 수 없습니다 .
이렇게하면 새로운 계층 구조를 "다시 열 수"없습니다. 즉 B::foo
, 새로운 가상 계층 구조의 헤드에 독립적으로있을 수 있는 새로운 관련없는 기능 을 만들 수 있는 방법이 없습니다 . 함수가 최종되면 파생 클래스에서 다시 선언 할 수 없습니다.
마지막 키워드를 사용하면 가상 메서드를 선언하고 N 번 재정의 한 다음 '더 이상 재정의 할 수 없습니다'라고 명령 할 수 있습니다. 파생 클래스의 사용을 제한하는 데 유용하므로 "내 슈퍼 클래스를 사용하면이를 무시할 수 있지만 나에게서 파생 시키려면 할 수 없습니다!"라고 말할 수 있습니다.
struct Foo
{
virtual void DoStuff();
}
struct Bar : public Foo
{
void DoStuff() final;
}
struct Babar : public Bar
{
void DoStuff(); // error!
}
다른 포스터에서 지적했듯이 비가 상 기능에는 적용 할 수 없습니다.
최종 키워드의 한 가지 목적은 우발적 인 메소드 재정의를 방지하는 것입니다. 필자의 예제에서 DoStuff ()는 파생 된 클래스가 단순히 올바른 동작을 얻기 위해 이름을 변경해야하는 도우미 함수일 수 있습니다. 최종적으로는 테스트 할 때까지 오류가 발견되지 않습니다.
함수에 추가 될 때 C ++의 최종 키워드는 기본 클래스에 의해 재정의되는 것을 방지합니다. 또한 클래스에 추가되면 모든 유형의 상속을 방지합니다. 최종 지정자를 사용하는 다음 예를 고려하십시오. 이 프로그램은 컴파일에 실패합니다.
#include <iostream>
using namespace std;
class Base
{
public:
virtual void myfun() final
{
cout << "myfun() in Base";
}
};
class Derived : public Base
{
void myfun()
{
cout << "myfun() in Derived\n";
}
};
int main()
{
Derived d;
Base &b = d;
b.myfun();
return 0;
}
또한:
#include <iostream>
class Base final
{
};
class Derived : public Base
{
};
int main()
{
Derived d;
return 0;
}
Mario Knezović의 답변 보충 :
class IA
{
public:
virtual int getNum() const = 0;
};
class BaseA : public IA
{
public:
inline virtual int getNum() const final {return ...};
};
class ImplA : public BaseA {...};
IA* pa = ...;
...
ImplA* impla = static_cast<ImplA*>(pa);
//the following line should cause compiler to use the inlined function BaseA::getNum(),
//instead of dynamic binding (via vtable or something).
//any class/subclass of BaseA will benefit from it
int n = impla->getNum();
위의 코드는 이론을 보여 주지만 실제 컴파일러에서는 실제로 테스트되지 않았습니다. 누군가가 분해 된 출력을 붙여 넣으면 많은 감사를드립니다.
virtual
키워드 를 사용하는지 여부에 관계없이 재정의 함수는 암시 적으로 가상 입니다.