2014-08-28 2 views
5

현재 다형성 유형과 할당 연산 간의 상호 작용을 조사하고 있습니다. 내 주요 관심사는 누군가가 기본 클래스의 값을 파생 클래스의 객체에 할당하여 문제를 일으킬 수 있는지 여부입니다.파생 클래스를 가리키는 참조에 대한 기본 클래스의 할당을 확인합니다.

this answer 기본 클래스의 대입 연산자는 파생 클래스의 암시 적으로 정의 된 대입 연산자에 의해 항상 숨겨집니다. 따라서 간단한 변수에 대입 할 때 타입이 잘못되면 컴파일러 오류가 발생합니다.

class A { public: int a; }; 
class B : public A { public: int b; }; 
int main() { 
    A a; a.a = 1; 
    B b; b.a = 2; b.b = 3; 
    // b = a; // good: won't compile 
    A& c = b; 
    c = a; // bad: inconcistent assignment 
    return b.a*10 + b.b; // returns 13 
} 
할당이 형태의 가능성이 객체 상태를 inconcistent으로 이어질 것

는, 그러나 더 컴파일러 경고가없는 및 ​​코드가 처음에 나에게 비 악마 같습니다 할당이 참조를 통해 발생하는 경우,이 사실이 아니다 섬광.

그런 문제를 감지 할 수있는 확립 된 관용구가 있습니까?

나는 잘못된 감지를 발견하면 예외를 던져 런타임 감지가 가능할 것으로 기대한다. 지금 생각할 수있는 최선의 방법은 기본 클래스에있는 사용자 정의 assigment 연산자입니다. 실행 시간 형식 정보를 사용하여 this이 실제로 파생 클래스가 아닌 base의 인스턴스에 대한 포인터인지 확인한 다음 수동으로 구성원별로 사본을 만듭니다. 이것은 많은 오버 헤드처럼 들리며 코드 가독성에 심각한 영향을줍니다. 더 쉬운 것이 있습니까?

편집 : 일부 접근법의 적용 가능성은 내가하고 싶은 것에 따라 다르기 때문에 여기에 몇 가지 세부 사항이 있습니다.

나는 두 가지 수학적 개념, 예를 들어 ringfield을 가지고 있습니다. 모든 필드는 링이지만 반대가 아닙니다. 각각에 대해 여러 가지 구현이 있으며 공통 기본 클래스, 즉 AbstractRingAbstractField을 공유합니다. 후자는 전자에서 파생됩니다. 이제는 std::shared_ptr을 기반으로하는 쉬운 참조 작성법을 구현하려고합니다. 그래서 내 Ring 클래스에는 std::shared_ptr<AbstractRing> 구현이 포함되어 있으며이를 전달하는 많은 방법이 포함되어 있습니다. FieldRing에서 상속 받아 작성하고 싶습니다. 그런 방법을 반복 할 필요가 없습니다. 필드에 특정한 메소드는 단순히 포인터를 AbstractField으로 캐스트 할 것이고 정적으로 캐스트하려고합니다. 나는 포인터가 실제로 건설 중 AbstractField인지 확인할 수 있습니다. 그러나 누군가가 을 인 Ring&에 할당 할 것이므로 걱정입니다. 따라서 포함 된 공유 포인터에 대한 내 가정 된 불변성이 깨졌습니다.

+0

추상적이지 않은 기본 클래스가있는 것이 진짜 문제는 아닌가요? –

+1

개인적으로 다형성 유형의 복사 생성자와 대입 연산자를 비활성화합니다. 상속 기본 다형성은 값 유형과 관련하여 실제로 잘 작동하지 않습니다. –

+0

@OliCharlesworth : 어떻게 보이지 않습니다. 예를 들어 생각해보십시오. 버튼에서 파생 된 토글 버튼을 사용하면 기본 클래스를 인스턴스화 할 수 있어야하고 실제 세계에서 문제가 발생할 수있는 상황을 볼 수 있습니다. 그러므로 나는 모든 "추상적 인 기반이되어야한다"는 접근을 따르지 않을 것이다. 이것이 당신이 염두에 두지 않은 것이라면, 추상적 기본 수업이 어떻게 나의 상황을 도울 수 있는지 자세히 설명해주십시오. – MvG

답변

1

컴파일 타임에 다운 캐스트 유형 참조에 대한 할당을 감지 할 수 없으므로 동적 솔루션을 제안합니다. 드문 경우이지만 일반적으로이 문제에 반대하지만 가상 할당 연산자를 사용해야 할 수도 있습니다.

class Ring { 
    virtual Ring& operator = (const Ring& ring) { 
     /* Do ring assignment stuff. */ 
     return *this; 
    } 
}; 

class Field { 
    virtual Ring& operator = (const Ring& ring) { 
     /* Trying to assign a Ring to a Field. */ 
     throw someTypeError(); 
    } 

    virtual Field& operator = (const Field& field) { 
     /* Allow assignment of complete fields. */ 
     return *this; 
    } 
}; 

이것은 아마도 가장 현명한 접근 방법입니다.

대안을 추적 할 수있는 참조 용 템플릿 클래스를 만들고 기본 포인터 * 및 참조 &의 사용을 간단히 금지 할 수 있습니다. 템플릿 솔루션은 올바르게 구현하는 것이 까다로울 수 있지만 다운 캐스트를 금지하는 정적 유형 확인을 허용합니다. 다음은 GCC 4.8과 -std = C++ 11 플래그 (static_assert의 경우)를 사용하여 "noDerivs (b)"가 오류의 원인이되는 컴파일 오류를 최소한 저에게 제공하는 기본 버전입니다.사용자가 처음 자신의 참조를 작성해, 인수로서 것을 통과하면

#include <type_traits> 

template<class T> 
struct CompleteRef { 
    T& ref; 

    template<class S> 
    CompleteRef(S& ref) : ref(ref) { 
     static_assert(std::is_same<T,S>::value, "Downcasting not allowed"); 
     } 

    T& get() const { return ref; } 
    }; 

class A { int a; }; 
class B : public A { int b; }; 

void noDerivs(CompleteRef<A> a_ref) { 
    A& a = a_ref.get(); 
} 

int main() { 
    A a; 
    B b; 
    noDerivs(a); 
    noDerivs(b); 
    return 0; 
} 

이 특정 템플릿

는 여전히 바보가 될 수있다. 결국 바보 같은 일을하지 않도록 사용자를 지키는 것은 절박한 시도입니다. 때때로 당신이 할 수있는 일은 공정한 경고를하고 상세한 모범 사례 문서를 제시하는 것입니다.

+0

흥미 롭습니다. C++ 11에서'Ring :: operator = (...) '구현 안에'typeid (* this) == typeid (Ring)'을 할 수 있습니다. 이것은 내가 염두에 두었던 것입니다. 가상 운영자의 성능이 좋든 나쁘 든간에 어느 날을 테스트해야합니다. "이것은 자바의 표준 할당 방법입니다."그러나 Java는 참조 의미론을 가지고 있으므로 여기서 어떤 종류의 과제를 언급하는지는 알지 못합니다. 배열 구성원의 할당은 가까워 지지만 여전히 나에게는 다릅니다. Java 측면은 아마도 주제가 아닐지 모르지만 나는 궁금합니다. – MvG

+0

@MvG typeid 비교는 런타임에 RTTI를 사용하여 수행됩니다. 가상 할당은 클래스 가상 테이블을 사용하며 비교가 필요하지 않습니다. 일반적으로 가상 멤버는 C++에서 선호되지만 RTTI가 상당한 오버 헤드를 제공하는지는 잘 모르겠습니다. 가상 호출은 -single-pointer indirection이며 RTTI는 더 많은 것을 포함 할 수 있습니다 (확실하지는 않습니다). 자바가 녹슨 것 같습니다. 나는 Object.equals와 .clone을 할당하는 것이 아니라, 비슷한 의미를 가지고 있음을 의미한다고 생각한다. – Zoomulator

+0

방금 ​​컴파일 시간을 의미 할 때 런타임을 썼다는 것을 알았습니다! – Zoomulator

관련 문제