2009-11-30 4 views
3

두 개의 기존 C++ 클래스에 스왑 기능을 추가하고 싶습니다. 한 클래스는 다른 클래스로부터 상속받습니다. 각 클래스의 인스턴스를 같은 클래스의 인스턴스로만 교체 할 수 있기를 바랍니다. 반 콘크리트로 만들려면 Foo and Bar 클래스가 있다고 말하십시오. Bar는 Foo를 상속합니다. 나는 Foo :: swap (Foo &)과 Bar :: swap (Bar &)을 정의한다. Bar :: 대리인을 Foo :: Swap에 스왑합니다. 나는 Foo :: Foo 인스턴스와 Bar :: swap 만 Bar 인스턴스에서 작업하도록 바꾸기를 원합니다.이 요구 사항을 적용하는 방법을 파악할 수 없습니다. 출력 여기상속 시나리오에서의 C++ 스왑 문제

#include <algorithm> 
#include <iostream> 

struct Foo { 
    int x; 
    Foo(int x) : x(x) {}; 

    virtual void swap(Foo &other) { 
     std::cout << __PRETTY_FUNCTION__ << std::endl; 
     std::swap(this->x, other.x); 
    }; 
}; 

struct Bar : public Foo { 
    int y; 
    Bar(int x, int y) : Foo(x), y(y) {}; 

    virtual void swap(Bar &other) { 
     std::cout << __PRETTY_FUNCTION__ << " "; 
     Foo::swap(other); 
     std::swap(this->y, other.y); 
    }; 
}; 

void display(Foo &f1, Foo &f2, Bar &b34, Bar &b56) 
{ 
    using namespace std; 

    cout << "f1: " << f1.x     << endl; 
    cout << "f2: " << f2.x     << endl; 
    cout << "b34: " << b34.x << " " << b34.y << endl; 
    cout << "b56: " << b56.x << " " << b56.y << endl; 
} 

int main(int argc, char **argv) 
{ 
    { 
     Foo f1(1), f2(2); 
     Bar b34(3,4), b56(5,6); 
     std::cout << std::endl << "Initial values: " << std::endl; 
     display(f1,f2,b34,b56); 
    } 

    { 
     Foo f1(1), f2(2); 
     Bar b34(3,4), b56(5,6); 
     std::cout << std::endl << "After Homogeneous Swap: " << std::endl; 
     f1.swap(f2);    // Desired 
     b34.swap(b56);   // Desired 
     display(f1,f2,b34,b56); 
    } 

    { 
     Foo f1(1), f2(2); 
     Bar b34(3,4), b56(5,6); 
     std::cout << std::endl << "After Heterogeneous Member Swap: " << std::endl; 
     // b56.swap(f2);   // Doesn't compile, excellent 
     f1.swap(b34);   // Want this to not compile, but unsure how 
     display(f1,f2,b34,b56); 
    } 

    return 0; 
} 

것 :

Initial values: 
f1: 1 
f2: 2 
b34: 3 4 
b56: 5 6 

After Homogeneous Swap: 
virtual void Foo::swap(Foo&) 
virtual void Bar::swap(Bar&) virtual void Foo::swap(Foo&) 
f1: 2 
f2: 1 
b34: 5 6 
b56: 3 4 

After Heterogeneous Member Swap: 
virtual void Foo::swap(Foo&) 
f1: 3 
f2: 2 
b34: 1 4 
b56: 5 6 
f1.swap은 (B34) "슬라이스"당신은 최종 출력 그룹에서 볼 수

B34 여기

나에게 문제를주고 있는지의 샘플입니다 잠재적으로 불쾌한 방법으로 유죄 선을 런타임에 컴파일하거나 분해하지 말아야합니다. 상속 때문에, 비회원 또는 친구 스왑 구현을 사용하면 같은 문제가 발생한다고 생각합니다.

이 코드는 도움이 될 경우 codepad에 있습니다.

이 사용 사례는 I want to add swap to boost::multi_array and boost::multi_array_ref이므로 발생합니다. multi_array는 multi_array_ref에서 상속됩니다. multi_array와 multi_array_refs를 multi_array_refs로 바꾸는 것만으로도 의미가 있습니다.

답변

3

(약간 해키 솔루션)

바에서, 보호 된 가상 메서드, isBaseFoo()를 추가는 푸에 true를 돌려 만들고, 거짓, 푸의 스왑 방법을 확인할 수 있습니다 그것은 인수가 isBaseFoo을 가지고 있어요() == true.

악마는 런타임에만 문제를 감지하지만 더 나은 것을 생각할 수는 없습니다.하지만 찰스 베일리의 대답은 dynamic_cast <>을 허용하면 더 좋을 수도 있습니다.

+0

분명히 못 생겼지 만 이것이 실제로 실용 가능한 유일한 것 같아요. 감사합니다 더글러스. –

2

당신은 정말로 이것을 할 수는 없지만, 어쨌든 나는 그 점을 보지 못합니다. operator= 또는 복사 생성자에서 슬라이스하는 것보다 나쁘지 않으며 후자도 피할 수 없습니다. swap이 다른 이유는 무엇입니까?

같은 이유에서 swap을 가상으로 만드는 것은 가치가 없을 가능성이 높습니다. 동일한 이유로 operator=을 가상으로 설정하지 않아도됩니다.

+1

많은 다른 장소에있는 표준 슬라이싱 문제입니다. 사람들의 문제가 이미이 문제에 관해 제기되었을지도 모르기 때문에 나는 지금 그것에 대해 훨씬 덜 걱정하고 있습니다. –

5

스왑은 할당 및 비교가 값 유형과 잘 작동하며 클래스 계층 구조의 기반과 잘 작동하지 않습니다.

저는 항상 콘크리트 클래스에서 파생되지 않고 리프 클래스 만 콘크리트로 만드는 Scott Meyer의 Effective C++ 권장 사항을 따르는 것이 가장 쉬운 것으로 나타났습니다. 그런 다음 리프 노드에만 가상이 아닌 함수로 swap, operator == 등을 안전하게 구현할 수 있습니다.

가상 스왑 기능을 사용할 수 있지만 가상 기본 클래스가있는 모든 지점은 런타임에 동적 인 동작을 수행하므로 컴파일 타임에 모든 잘못된 가능성을 얻으려는 실패자가 있다고 생각합니다.

가상 스왑 경로를 원할 경우 가능한 한 가지 방법은 이와 같은 작업을 수행하는 것입니다.

class Base 
{ 
public: 
    virtual void Swap(Base& other) = 0; 
}; 

class ConcreteDerived 
{ 
    virtual void Swap(Base& other) 
    { 
     // might throw bad_cast, in this case desirable 
     ConcreteDerived& cother = dynamic_cast<ConcreteDerived&>(other);bad_cast 
     PrivateSwap(cother); 
    } 

    void PrivateSwap(ConcreteDerived& other) 
    { 
     // swap implementation 
    } 
}; 
+0

제안 해 주셔서 감사합니다, Charles. 내 유스 케이스 때문에 Base :: Swap이 아닌 가상이 필요하다. ConcreteDerived :: Swap과 비슷한 Base :: Swap과 같은 동적 캐스트가 항상 성공하기 때문에 이것을 채택 할 수 있을지 확신 할 수 없습니다. –

+0

기본 클래스 스왑이 가상이 아닌 경우 슬라이스 방지 기능이 거의 없어도됩니다. 계층 구조에 추가하는 모든 클래스에는 슬라이스 할 기본 스왑 동작이 있습니다. 일종의 다형성 (polymorphic) 동작이 없으면 포인터 또는 기본 클래스에 대한 참조를 통해 두 개의 파생 클래스를 성공적으로 스왑 할 수 없습니다. 가상 함수는 이것을 수행하는 가장 자연스러운 방법입니다. –

0

실제로 수행하려고하는 것은 제 3의 상속 계층에서 클래스 인스턴스를 스왑하는 것입니다. 감안할 때, 나는 실제 클래스에서 스왑을 사용하고 간접적 인 수준을 추가하는 것을 잊어 버릴 것입니다. boost :: shared_ptr을 사용하는 것은 좋은 접근법입니다. 원하는 클래스를 포함하는 shared_ptr 인스턴스를 사용하고 마음의 콘텐츠로 바꾸십시오.

일반적으로 컴파일 타임에 질문했을 때 문제를 해결하는 것은 다른 응답자가 설명한 모든 이유 때문에 어렵습니다.

1

이 시나리오는 이제 C++ 11의 이동 의미 체계에 의해 처리된다고 생각합니다. 보통 스왑 함수를 사용하여 할당에서 복사 중복을 피하기 때문에 스왑 함수를 확장해야하는 파생 클래스에서 정적으로 만 사용되며 해당 정적 유형을 알고 있으므로 가상의 필요성이 없습니다. 그것이 명시된대로 슬라이싱에 미묘한 문제가 발생할 수 있습니다). 사실 나는 다른 곳에서 직접적으로 사용되지 않도록 스왑 함수를 기반의 보호 된 메서드로 선언합니다. 가장 구체적인 클래스는 필요한 경우 최종 버전 공개를 가질 수 있습니다.