2010-11-18 2 views
1

필자가 현재 작성중인 응용 프로그램에서 순수 가상 함수로 템플릿 클래스를 만든 다음 가상 클래스의 인스턴스를 상속하고 가상 함수를 구현하는 다른 클래스를 만들었습니다. 가상 함수는 부모 생성자로부터 호출되며 자식 생성자도이를 사용합니다. 링커 오류로 인해이 코드를 작성할 수없고 그 이유를 알 수 없습니다. 다음은 내가 가지고있는 문제를 재현 할 수있는 단순화 된 코드입니다. (나는 푸의 생성자에서() 에코에 대한 호출을 이동하면템플릿 내에서 순수 가상 함수를 사용하여 C++ 코드를 작성하려고 할 때 링커 오류가 발생하는 이유는 무엇입니까?

purevirttest.obj : error LNK2019: unresolved external symbol "protected: virtual void __thiscall Foo::echo(void)" ([email protected][email protected]@@MAEXXZ) referenced in function "public: __thiscall Foo::Foo(int)" ([email protected]@@[email protected]@Z)

이 코드는 내가 bar.echo 호출 할 수 있습니다, 구축하고 잘 실행 : MSVC에서 다음과 같이

template <typename T> class Foo 
{ 
public: 
    Foo(T a) 
    { 
     x = a; 
     echo(); 
    } 

protected: 
    T x; 
    virtual void echo() = 0; 
}; 

class Bar : public Foo<int> 
{ 
public: 
    Bar(int a) : Foo<int>(a) 
    { 
    } 

    void echo(); 
}; 

void Bar::echo() 
{ 
    cout << "value: " << x << endl; 
} 

int main(int argc, char* argv[]) 
{ 
    Bar bar(100); 
    return 0; 
} 

링커 오류가 나타납니다) 아무 문제없이. 문제는 내가 생성자에서 그 함수를 정말 좋아할 것입니다. 이 수수께끼의 모든 설명 higly 감사.

답변

4

제임스 McNellis '대답은 거의 올바른 입니다.

Foo<T> 생성자의 본문이 Foo<T> 유형의 개체이기 때문에 실제로는 Foo<T> 생성자에서 호출 할 수 없습니다. 파생 된 클래스 부분은 아직 없습니다. 그리고 코드에서와 마찬가지로 echo()의 가상 호출은 순수한 가상 함수 인 bang, dead로 이동합니다.

그러나 echo() 같은 순수 가상 함수의 구현을 제공 할 수 있습니다, 다음 Foo 생성자에서, Foo::echo() 같은 비 실질적으로 호출. :-) 그 것을 제외하면 Foo 구현을 호출합니다. 파생 클래스의 구현을 호출하고 싶습니다.

"I'd really like that function in the constructor."

글쎄, 나는이 당신의 (잘못된) 코드를 쓰고 있어요으로하는 것은 다음과 같습니다 : 이제 당신의 문제에 대한

template <typename T> class Foo 
{ 
public: 
    Foo(T a) 
    { 
     x = a; 
     echo(); 
    } 

protected: 
    T x; 
    virtual void echo() = 0; 
}; 

class Bar : public Foo<int> 
{ 
public: 
    Bar(int a) : Foo<int>(a) 
    { 
    } 

    void echo(); 
}; 

void Bar::echo() 
{ 
    cout << "value: " << x << endl; 
} 

int main(int argc, char* argv[]) 
{ 
    Bar bar(100); 
    return 0; 
} 

그리고 지금까지 내가 당신의 문제 설명을 이해, Foo 생성자가 Foo에서 상속하는 모든 클래스의 echo 구현을 호출하게합니다.

이렇게하는 방법에는 여러 가지가 있습니다. 그들은 모두 파생 클래스의 구현에 대한 지식을 기본 클래스에 가져 오는 것에 관한 것입니다.

하나가이 같이 갈 수 CRTP호기심 반복 템플릿 패턴로 알려져 있으며, 특정 문제에 적응 :

#include <iostream> 

template< class XType, class Derived > 
class Foo 
{ 
public: 
    Foo(XType const& a) 
     : state_(a) 
    { 
     Derived::echo(state_); 
    } 

protected: 
    struct State 
    { 
     XType x_; 
     State(XType const& x): x_(x) {} 
    }; 

private: 
    State state_; 
}; 

class Bar 
    : public Foo< int, Bar > 
{ 
private: 
    typedef Foo< int, Bar >  Base; 
public: 
    Bar(int a): Base(a) {} 
    static void echo(Base::State const&); 
}; 

void Bar::echo(Base::State const& fooState) 
{ 
    using namespace std; 
    cout << "value: " << fooState.x_ << endl; 
} 

int main() 
{ 
    Bar bar(100); 
} 

을 위 나쁘지 않다 솔루션이며, 그러나 좋지도 않습니다. 실제 문제가 기본 클래스 생성자에서 파생 클래스 비 정적 멤버 함수를 호출하는 경우 유일한 "좋은"대답은 Java 또는 C#이므로 이러한 작업을 수행 할 수 있습니다. 의도적으로 C++에서 지원되지 않습니다. 파생 클래스 객체에서 아직 초기화되지 않은 항목에 실수로 액세스하려고 시도하기가 쉽기 때문입니다.

어쨌든 거의 항상 그렇듯이 컴파일 타임 솔루션이있는 곳에서는 런타임 솔루션이 있습니다.

당신은 단순히 기능을 전달할 수

은과 같이, 생성자의 인수로 실행되는 : 당신이 실행에 비해 컴파일 시간 이외에 (당신은 아마 미묘한 차이를 알게 될 것이다이 두 프로그램을 연구하면

#include <iostream> 

template< class XType > 
class Foo 
{ 
protected: 
    struct State 
    { 
     XType x_; 
     State(XType const& x): x_(x) {} 
    }; 

public: 
    Foo(XType const& a, void (*echo)(State const&)) 
     : state_(a) 
    { 
     echo(state_); 
    } 

private: 
    State state_; 
}; 

class Bar 
    : public Foo<int> 
{ 
private: 
    typedef Foo<int> Base; 
public: 
    Bar(int a): Base(a, echo) {} 
    static void echo(Base::State const&); 
}; 

void Bar::echo(Base::State const& fooState) 
{ 
    using namespace std; 
    cout << "value: " << fooState.x_ << endl; 
} 

int main() 
{ 
    Bar bar(100); 
} 

시간 지식 이전).

마지막으로 더티 캐스트가 포함 된 솔루션이 있으며 멤버 포인터를 사용하여 캐스팅하지 않고 보호 된 기본 클래스 상태에 액세스 할 수있는 C++ 유형 시스템의 허점이 있습니다. 전자는 위험하며 후자는 불분명하고 비효율적 일 수 있습니다. 그러지 마.

하지만 위의 해결 방법 중 하나가 적합하거나 적합한 적응 방법 일 것입니다.

아, 그런데, 문제의 일반적인 세트 당신은 동적 초기화 동안 바인딩 DBDI, 로 알려져,의 인스턴스로 보인다. C++ FAQ 항목 23.6 Okay, but is there a way to simulate that behavior as if dynamic binding worked on the this object within my base class's constructor?에서 더 일반적인 취급 방법을 찾을 수 있습니다. 또한 파생 클래스에서 기본 클래스 구성의 일부를 제어/제공하려는 DBDI의 경우에는 내 블로그 항목 "How to avoid post-construction by using Parts Factories"을 참조하십시오.

건배 & HTH., 나도 몰라

4

Foo<T>의 생성자에서 echo()을 호출 할 수 없습니다.

Foo<T>의 생성자 안에있는 객체의 동적 유형은 Foo<T>입니다. Foo<T> 생성자가 동적 유형이 Bar이 될 때까지는 그렇지 않습니다. echo() 이후

Foo<T> 가상 순수하고 Foo<T> 객체의 동적 타입이기 때문에, 당신은 Foo<T>의 생성자에서 echo()를 호출 할 수 없습니다.

생성 및 삭제 중에 동적 유형의 객체가 변경되는 방식을 잘 알고 있지 않다면 생성자와 소멸자에서 가상 함수를 호출하지 않는 것이 좋습니다. "당신은 Foo<T>의 생성자에서 echo()를 호출 할 수 없습니다"라는

+0

는 누가 -1'd하지만, 귀하의 의견은 바로 나에게 읽습니다. G ++은'warning : abstract virtual 'void Foo :: echo() [with T = int]'가 생성자'에서 호출 된 다음 오류를 발생시킵니다 :'test.cpp :(. text._ZN3FooIiEC2Ei [ Foo :: Foo (int)] + 0x2c) : Foo :: echo() '에 대한 정의되지 않은 참조 – Ashe

+0

비논리적 인 음색은 필요하지 않지만이 대답은 정확하므로 upvoting을합니다. 바꿔 말하면 가치가 있을지도 모릅니다. 기본적으로 Bar의 생성자가 객체 생성시 Foo의 생성자를 호출하면 Foo는 Bar에 대해 아무 것도 모릅니다. RTTI가 적절한 가상 기능을 시작하고 호출 할 수있는 객체가 완전히 만들어지기 전까지는 아닙니다. – rcv

관련 문제