2013-04-05 2 views
0

나는 다음과 같은 계층 구조 한 : 이제설계 및 데이터의 초기화

Base_class 
     | 
Traits_class 
     | 
Concrete_class 

는 것은 데이터에 포함되는 Base_class은 (는 Traits_class가 액세스 권한을 가지고 있기 때문에 거기에있을 필요가있는 그것. Traits_class 전달 된 템플릿 매개 변수에 따라 다른 기능을 클래스 템플릿 (그래서 나는 다른 클래스에 대한 부분 템플릿 특수화를 사용합니다.) 마지막으로, 가장 낮은 수준에서 Concrete_class 또한 클래스 템플릿입니다. 나는 Concrete_class 인스턴스를 만듭니다 only.

이제 질문은 : 모든 생성자와 소멸자를 작성했으며 Concrete_class 내에 이동 의미를 제공했습니다. 즉, 기본 생성자를 호출하지 않지만 파생 클래스에서 직접 상태를 초기화합니다. 이 문제가 있으면 누군가가 지적 할 수 있습니까? 소멸자 만이 Base_class에 선언되고 보호 된 것으로 선언됩니다. 이 디자인에 결함이 있습니까?

통찰력을 가져 주셔서 감사합니다. 편집

그래서 나는 CRTP에 Yakk의 코멘트 다음 디자인을 수정, 지금은 나 또한 내가 할 수있는 CRTP에 모든 Concrete_class 데이터, 감사를 이동 한

Traits_class 
     | 
Concrete_class 

Traits_class에서 액세스 할 수 있습니다. 이상한 일이 있었는데 Traits_class의 생성자 내에서 Traits_class의 데이터에 액세스 할 수 없었기 때문에 이상한 일이 발생했습니다. 내 말은, 나는 그것에 접근했는데, 나는 마치 Traits_class (그리고 심지어는 Traits_class 생성자 내에서 인쇄 된) 멤버들을 초기화했기 때문에 고스트 데이터에 액세스하는 것처럼 보였지만, 그 다음에 클래스는 비어 있었다. 그래서 나는 실제로 무슨 일이 일어 났는지 이해하지 못한다. (나는 Traits_class를 Concrete_class에 const_casting하여 이것을 수행했다.)

결국 나는 Traits_class에 정적 멤버 함수를 작성하여 Concrete_class의 멤버를 초기화했습니다. 나는 보호 된 멤버 함수를 사용하여 같은 일을 할 수 있었을 것이라고 생각한다. (왜냐하면 나는 Traits_class을 상속 받았기 때문이다.) 그러나 나는 똑같은 것이라고 생각한다.

추가 의견이 있으시면 알려 주시기 바랍니다. C++ 지혜에 다시 한 번 감사드립니다.

aa

+0

"기본 생성자를 호출하지 않습니다."- 나는 그렇게 생각합니다. 파생 클래스 생성자의 이니셜 라이저 목록에서 호출 할 생성자에 대해 언급하지 않으면 빈 기본 생성자를 호출합니다. CRTP는 파생 클래스의 데이터에'Traits_class' 접근을 허용 할 수 있습니다. – Yakk

+0

내 게시물에 회신 해 주신 Yakk에게 감사드립니다. CRTP에 익숙하지만 파생 클래스에 대한 참조를 저장해야합니다. 맞습니까? 아니면 다른 방법으로 데이터에 액세스 할 수 있습니까? – aaragon

+2

'템플릿 구조체 CRTP_example {D * self() {return static_cast (this); D const * self() const {return static_cast (this); } int getx() const {return self() -> x; }}; 구조체 파생 된 : CRTP_example {int x; 파생 된() : x (7) {}};'- 관련 참조 저장이 없습니다. 'CRTP_example '도'D'의 기본 클래스 인'static_assert'를 원할 것입니다. – Yakk

답변

3

추론에 오류가 있습니다. 생성자는 항상 모든 기본 클래스와 비 정적 멤버를 초기화합니다 (기술적으로 가상베이스는 다른 기본 생성자가 아닌 가장 파생 된 유형으로 초기화 됨). 따라서 Base_class는 컴파일러에서 생성 한 기본 생성자에 의해 실제로 초기화됩니다 (또는 복사 또는 이동을 수행하는 경우 컴파일러에서 생성 한 복사/이동 생성자). 기본 (또는 복사/이동) 생성자를 사용하여 모든 데이터 멤버를 초기화합니다. 나중에 구체적인 클래스의 생성자에 해당 멤버를 할당 할 수 있지만 초기화는 이미이 시점까지 발생했습니다.

기본 클래스는 모든 데이터 멤버를 소유하므로 실제로 복사 또는 이동이 발생할 때 모든 데이터 멤버를 초기화하는 기본 클래스가됩니다.가장 파생 된 클래스에서 자신 만의 복사본이나 이동 생성자를 작성하는 경우 초기화 목록에서 기본 클래스의 복사/이동 생성자를 호출해야합니다. 그렇지 않으면 데이터 멤버가 기본으로 생성되어 강제로 사실 이후에 복사/이동 할당을 사용하십시오. 이것은 종종 비효율적이며 어떤 경우에는 올바르지 않을 수 있습니다. 예를 들어 움직일 수있는 클래스를 작성했지만 슬라이싱 문제로 인해 이동할 수 없습니다. 데이터 멤버로 Base_class에 그러한 클래스가 있으면 move semantics만을 구현할 수 없습니다

모든 데이터 멤버를 Concrete_class에서 초기화해야하는 경우 모든 데이터 멤버를 값으로 가져 와서 해당 데이터 멤버로 이동하고 완벽하게 지원하는 Base_class의 보호 된 생성자를 제공하는 것이 좋습니다. Traits_class에서 전달 생성자를 사용하거나 (이 지원 컴파일러를 사용하는 경우에는 기본 생성자를 상속받습니다). 이를 통해 concrete 클래스는 데이터 멤버를 초기화하는 값을 지정할 수 있지만 기본 클래스는 실제 초기화를 수행 할 수 있습니다. 또한 Base_class 및 Traits_class 생성자는 완전히 초기화 된 데이터 멤버에 액세스 할 수 있습니다 (그렇지 않으면 기본 멤버 초기화 상태의 데이터 멤버에만 액세스 할 수 있습니다). 사이드 참고로

 
struct Base_class { 
protected: 
    Base_class(string s) : s_(move(s)) { } 
    ~Base_class() = default; 
    // Request copy/move, since we're declaring our own (protected) destructor: 
    Base_class(Base_class const &) = default; 
    Base_class(Base_class &&) = default; 
    Base_class &operator=(Base_class const &) = default; 
    Base_class &operator=(Base_class &&) = default; 
      
    string s_; 
}; 
  
template<int> 
struct Traits_class : Base_class { 
protected: 
    template<typename... P> 
    Traits_class(P &&p) 
     : Base_class(forward<P>(p)...) 
    { } 
}; 
  
template<int I> 
struct Concrete_class : Traits_class<I> { 
    Concrete_class(char c) 
     : Traits_class<I>(string(I, c)) 
    { } 
}; 

, 데이터는 반드시 액세스 할 수 있도록 Traits_class에 대한 BASE_CLASS에있을 필요하지 않습니다

다음은 예입니다. 가상 함수 호출의 오버 헤드에 신경 쓰지 않고 생성자 나 소멸자 내에서 액세스 할 필요가없는 경우 보호 된 가상 함수를 통해 액세스를 제공 할 수 있습니다 (사용자가 가정하지 않은 경우 현재 데이터 멤버는 Concrete_class의 생성자가 실행될 때까지 최종 값을 갖지 않습니다. 가상 호출 오버 헤드를 피하기 위해 Curiously Recurring Template Pattern을 @Yakk가 언급 한대로 사용할 수 있습니다.

== 응답은 원래의 질문 ==에서 편집

데이터가 파생 클래스에 저장되어있는 경우, 그것은 기본에 초기화되지 않은 수 있도록 기본 클래스의 생성자는 파생 클래스의 생성자 전에 실행됩니다 클래스의 생성자 (이미 소멸자에서 회수). 생성자는 기본 클래스의 인스턴스를 가져 와서 클래스의 파생 된 부분을 초기화하여 파생 클래스의 인스턴스로 만들고 (특별한 경우로는 기본이없는 클래스의 생성자라고 생각할 수 있습니다. 클래스는 "아무것도"(원시 저장소)를 클래스의 인스턴스로 바꿉니다.

traits 클래스 생성자가 실행 중일 때 아직 구체적인 파생 클래스가 아닙니다. 따라서 파생 클래스의 데이터 멤버에 액세스하거나 클래스를 파생 클래스로 사용하는 것은 불법입니다. 언어는 가상 기능을 위해 이것을 시행합니다. 기본 클래스의 생성자 또는 소멸자 내에서 가상 함수를 호출하면 함수의 기본 버전이 호출됩니다. 예 :

#include <iostream> 
using namespace std; 

struct Base { 
    Base() { cout << "Base ctor\n"; v(); } 
    ~Base() { v(); cout << "Base dtor\n"; } 
protected: 
    virtual void v() const { cout << "Base::v\n"; } 
}; 

struct Derived : Base { 
    Derived() { cout << "Derived ctor\n"; v(); } 
    ~Derived() { v(); cout << "Derived dtor\n"; } 
protected: 
    virtual void v() const { cout << "Derived::v\n"; } 
}; 

int main() { Derived d; } 

/* Output: 
Base ctor 
Base::v 
Derived ctor 
Derived::v 
Derived::v 
Derived dtor 
Base::v 
Base dtor 
*/