2010-01-12 2 views
2

레거시 프레임 워크에서 작업 중입니다. 'A'는 기본 클래스이고 'B'는 파생 클래스입니다. 두 클래스 모두 중요한 프레임 워크 초기화를 수행합니다. FWIW는 ACE 라이브러리를 많이 사용합니다.파생 클래스 생성자 문제에 대한 종속성

나는 상황이있다; 'B'의 인스턴스가 생성됩니다. 그러나 'A'의 ctor는 'B'에서만 수행 할 수있는 초기화에 따라 달라집니다.

'B'가 인스턴스화되면 'A'에 대한 식별자가 'B'식별자 전에 호출됩니다. static functions을 사용하는 virtual 메커니즘은 ctors에서 작동하지 않으므로 (static-initialization-order-fiasco으로 인해) 제외됩니다. 다음과 같이

은 내가 CRTP 패턴을 사용하여 고려 -

template<class Derived> 
class A { 
public: 
    A(){ 
    static_cast<Derived*>(this)->fun(); 
    } 
}; 

class B : public A<B> { 
public: 
    B() : a(0) { 
    a = 10; 
    } 
    void fun() { std::cout << "Init Function, Variable a = " << a << std::endl; } 
private: 
    int a; 
}; 

그러나 초기화 목록에 초기화 클래스 멤버는 아직 'A'에서 위의 경우 (철을 실행하지 않는 한 정의되지 않은 값을). 제 경우에는 그러한 프레임 워크 기반 초기화 변수가 여러 개 있습니다.

이 상황을 처리 할 수있는 잘 알려진 패턴이 있습니까? 사전에

감사합니다,


업데이트은 : dribeas에 의해 주어진 아이디어를 바탕으로

, 내가 연상 업이 문제에 대한 임시 해결책을 (본격적인 리팩토링 않습니다 현재 내 일정에 맞지 않습니다.) 다음 코드는 같은 보여줄 것입니다 : -

// move all A's dependent data in 'B' to a new class 'C'. 
class C { 
public: 
    C() : a(10) 
    { } 
    int getA() { return a; } 
private: 
    int a; 

}; 

// enhance class A's ctor with a pointer to the newly split class 
class A { 
public: 
    A(C* cptr) 
    { 
    std::cout << "O.K. B's Init Data From C:- " << cptr->getA() << 
std::endl; 
    } 

}; 

// now modify the actual derived class 'B' as follows 
class B : public C, public A { 
public: 
    B() 
    : A(static_cast<C*>(this)) 
    { } 

}; 

를 동일에 좀 더 설명은 m ++ c.l.c에 this 링크를 참조하십시오.. Konstantin Oznobikhin이 제시 한 멋진 일반적인 솔루션이 있습니다.

+2

UB (정의되지 않은 동작) 및 컴파일러에서 재생하는 다소 괴로운 속임수입니다. –

답변

3

아마 당신이 할 수있는 가장 좋은 일은 리팩토링입니다. 기본 클래스가 파생 된 유형 중 하나에 종속되도록하는 것은 이치에 맞지 않습니다.

이전에는 개발자에게 상당한 고통을주고 있습니다. ACE_Task 클래스를 확장하여 구체적인 기능으로 확장 할 수있는주기적인 스레드를 제공하고 정기적 인 스레드 생성자에서 스레드를 활성화하여 테스트에서 더 자주 작동하는 것보다 더 효과적이지만, 일부 상황에서는 스레드가 실제로 파생 된 객체가 초기화되기 전에 실제로 시작되었습니다.

상속은 필요한 경우에만 사용해야하는 강력한 관계입니다.당신이 부스트 스레드 라이브러리 (그냥 문서, 자세히 입력 할 필요가 없습니다) 또는 POCO 라이브러리를 살펴보면 두 가지로 문제가 나뉘는 것을 볼 수 있습니다 : 스레드 클래스는 스레드 실행을 제어하고 통과 된 메소드를 호출합니다 스레드 컨트롤은 실행될 실제 코드와 분리되어 있으며 실행될 코드가 생성자에 대한 인수로 수신된다는 사실은 스레드 생성자가 호출되기 전에 생성 된 코드임을 보장합니다.

아마 당신은 당신 자신의 코드에서 같은 접근법을 사용할 수 있습니다. 파생 된 클래스가 현재하고있는 것이 무엇이든, 계층 구조 외부로 이동해야하는 기능을 두 가지로 나눕니다 (부스트는 펑터를 사용하고, POCO는 인터페이스를 사용하며 가장 적합하다고 생각하는 것을 사용합니다). 당신이하려고하는 것에 대해 더 잘 설명하지 않으면, 나는 더 자세히 설명 할 수 없습니다.

시도해 볼 수있는 또 다른 방법은 B 클래스를 A 클래스와 독립적 인 C 클래스로 분해하는 것입니다. 두 클래스 모두 상속 한 B 클래스를 먼저 C에서 A로 상속합니다 (거기에 거대한 경고가 나온다.) 그러면 C가 A보다 먼저 생성됩니다. 그런 다음 C 하위 객체를 A의 인수로 만듭니다 (인터페이스 또는 템플릿 인수를 통해). 이것은 아마도 가장 빠른 해킹 일 것이지만 좋은 것은 아닙니다. 코드를 수정 한 후에는 올바르게 수정하십시오.

+0

제안에 감사드립니다. 나에 대한 주요 리팩토링 운동처럼 보인다. – Abhay

+0

@dribeas : 실제 생산 코드 관련 문제 (바보 같은 질문이라고 생각하는 일부와 달리)를 이해했을뿐만 아니라 임시 제안에 사용했던 좋은 제안 중 하나를 제공했습니다. 그래서 저는 이것을 대답으로 받아들입니다. 감사. – Abhay

2

우선, 기본 클래스의 생성자가 파생 된 생성자에서 수행되는 작업에 의존하면 디자인이 좋지 않다고 생각합니다. 그렇게해서는 안됩니다. 기본 클래스의 생성자가 실행될 때 파생 클래스의 객체는 기본적으로 존재하지 않습니다.

해결 방안은 파생 클래스에서 기본 클래스의 생성자로 전달 된 도우미 개체를 갖는 것일 수 있습니다.

+0

그것은 그것을 도울 수 없어! 그것의 '유산'응용 프레임 워크.나는 도우미 객체 접근법도 고려했지만, 내 경우에는 너무 많은 작업이다. 우리가 더 나은 대안을 찾으면 볼 수 있습니다. 감사합니다. – Abhay

2

아마도 Lazy Initialization입니다. A에 플래그를 저장합니다.이 플래그는 초기화됩니다. 메소드를 호출 할 때마다 플래그를 확인하십시오. false 인 경우 A를 초기화하고 (B의 ctor가 실행 된 경우) 플래그를 true로 설정합니다.

1

나쁜 디자인이며 이미 UB라고되어 있습니다. 이러한 종속성을 '초기화'라고하는 다른 메서드로 이동하고 파생 클래스 생성자 (또는 초기화 할 기본 클래스 데이터가 실제로 필요하기 전에 어느 곳에서든지)에서이 initialize 메서드를 호출하십시오.

0

흠. 그래서 올바르게 읽는다면 "A"는 레거시 코드의 일부이고, 어떤 문제에 대한 올바른 대답은 파생 클래스 B를 사용하는 것입니다.

가장 간단한 솔루션은 기능적 (OOP가 아닌) 스타일의 정적 팩토리 함수를 만드는 것일 수 있습니다.

static B& B::makeNew(...); 

정적 초기화 순서 오류가 발생하는 경우를 제외하고 는요? 이런 종류의 설정으로는 생각하지 않을 것입니다. 초기화 작업이 없기 때문입니다.

"C"는 "A"가 필요로하는 "B"에 의한 일부 설정을해야하며, "A"만이 상속을 원하기 때문에 "D"만 가져옵니다. 그래서 ... 가짜 상속은 건설 명령을 통제 할 수있는 방식으로 ...? 이 자세한이지만

class A 
{ 
    B* pB; 
public: 
    rtype fakeVirtual(params) { return pB->fakeVirtual(params); } 

    ~A() 
    { 
     pB->deleteFromA(); 
     delete pB; 
     //Deletion stuff 
    } 

protected: 
    void deleteFromB() 
    { 
     //Deletion stuff 
     pB = NULL; 
    } 
} 

class B 
{ 
    A* pA; 
public: 
    rtype fakeInheritance(params) {return pA->fakeInheritance(params);} 

    ~B() 
    { 
     //deletion stuff 
     pA->deleteFromB(); 
    } 

protected: 
    friend class A; 
    void deleteFromA() 
    { 
     //deletion stuff 
     pA = NULL; 
    } 
} 

, 나는이 안전하게 가짜 상속을해야한다고 생각하고, B는이 일이 수행 한 때까지 당신이를 구성하는 것을 기다릴 수 있습니다. 또한 캡슐화되었으므로 A를 가져올 수있을 때 A와 B 이외의 다른 값을 변경할 필요가 없습니다.

또는 몇 가지 단계를 거쳐 다시 질문 할 수도 있습니다. 상속이 내가 사용하려고하는 기능을 제공하는 기능은 무엇이며 다른 방법을 통해이를 달성 할 수 있습니까? 예를 들어 CRTP는 virtual 대신 사용할 수 있으며 정책은 함수 상속의 대안입니다. (나는 그게 올바른 표현이라고 생각한다). 위의 아이디어를 사용하고 있습니다. 템플릿을 삭제하면됩니다. B는 템플릿을 B로, 그 반대의 경우는 템플릿 만 기대합니다.

관련 문제