2010-01-29 3 views
3

함수 오버로드에 문제가 있습니다. 나는 몇 가지 간단한 예제를 보여줍니다 :함수 오버로드가있는 C++ 문제

class A {}; 
class B : public A{}; 

void somefunction(A&, A&); 
void somefunction(B&, B&); 

void someotherfunction() { 
... 
A& a1 = ... 
A& a2 = ... 

... 
} 

모두 A1과 A2는 B의 인스턴스 만

somefunction(a1,a2); 

전화

void somefunction(A&, A&); 

내가 뭘 잘못 했습니까입니까? 다형성과 과부하는 그런 것들을위한 거지, 그렇지?

편집 : 이제 알았습니다. 작동하지 않습니다. (답변 해 주셔서 감사합니다.)

어떻게 해결할 수 있습니까? 주조하지 않고.

edit2 : 좋아, 무언가를 갖고 싶기 때문에 타입 캐스팅으로 그대로 남겨 둡니다. 도와 주셔서 감사합니다.

답변

9

것은 정적을 캐스트. 컴파일러는 어떤 함수가 전달되는 타입을 기반으로 사용되는지를 선택합니다. C#은 정확히 같은 방식으로 동작합니다 (자바도 역시 그렇습니다).

당신이 잘못된 장소에서 다형성을 구현하고있는 것처럼 보입니다. somefunction은 실제로 클래스 a에 속하며 가상이어야합니다. 그런 다음 런타임에 a의 인스턴스에서 호출 될 때마다 올바른 클래스의 재정의가 호출됩니다.

class a { 
public: 
    virtual somefunction(a& a2) { 
    //do stuff 
    } 
} 

class b : public a { 
    virtual somefunction(a& a2) { 
    b& b2 = (b&)a2; 
    //do stuff 
    } 
} 


class c : public b { 
    virtual somefunction(a& a2) { 
    c& c2 = (c&)a2; 
    //do stuff 
    } 
} 

위의 솔루션은 가상 함수 내에서 최소한의 캐스팅을 사용하고 가정 동일한 유형의 두 가지 예를 그 :

그래서, 정말은 다음과 같이해야한다. 이는 b.somefunction(a())에 정의되지 않은 동작이 있음을 의미합니다.

더 나은 해결책은 C++ RTTI을 사용하고 다운 캐스팅이 불가능한 경우 NULL을 반환하는 dynamic_cast를 사용하는 것입니다.

이 문제는 double dispatch problem으로 알려져 있으며 위키 백과 문서에서 설명했습니다.또한 multiple dispatch에 대해 위키 백과에서 제공하는 유일한 해결책은 dynamic_cast입니다.

EDIT 좋아,이게 나를 괴롭혔다. 여기에 기본 클래스와 두 개의 하위 클래스 사이의 완전 이중 디스패치 솔루션이있다. 꽤 좋지는 않지만 친구 클래스와 같은 C++ 속임수를 사용합니다 (실제로는 역순보다 캡슐화가 더 잘 이루어집니다). 그리고 선언을 전달합니다.

class b; 
class c; 
class a { 
protected: 
    virtual void somefunction(a& a2); //do stuff here 
    virtual void somefunction(b& b2); //delegate to b 
    virtual void somefunction(c& c2); //delegate to c 
public: 
    virtual void doFunc(a& a2) { 
     a2.somefunction(*this); 
    } 
    friend class b; 
    friend class c; 
}; 

class b : public a { 
protected: 
    virtual void somefunction(a& a2); //do stuff here 
    virtual void somefunction(b& b2); //do stuff here 
    virtual void somefunction(c& c2); //delegate to c 
public: 
    virtual void doFunc(a& a2) { 
     a2.somefunction(*this); 
    } 
    friend class a; 
}; 


class c : public b { 
protected: 
    virtual void somefunction(a& a2); //do stuff here 
    virtual void somefunction(b& b2); //do stuff here 
    virtual void somefunction(c& c2); //delegate to c 
public: 
    virtual void doFunc(a& a2) { 
     a2.somefunction(*this); 
    } 
    friend class a; 
    friend class b; 

}; 
//class a 
void a::somefunction(a& a2) { 
    printf("Doing a<->a"); 
} 
void a::somefunction(b& b2) { 
    b2.somefunction(*this); 
} 
void a::somefunction(c& c2) { 
    c2.somefunction(*this); 
} 
//class b 
void b::somefunction(a& a2) { 
    printf("Doing b<->a"); 
} 
void b::somefunction(b& b2) { 
    printf("Doing b<->b"); 
} 
void b::somefunction(c& c2) { 
    c2.somefunction(*this); 
} 
//class c 
void c::somefunction(a& a2) { 
    printf("Doing c<->a"); 
} 
void c::somefunction(b& b2) { 
    printf("Doing c<->b"); 
} 
void c::somefunction(c& c2) { 
    printf("Doing c<->c"); 
} 
+0

내가 말했듯이, 나는 타입 캐스팅을 피하고 싶다.그리고 만약 내가 다른 클래스 C isa A와 또 다른 somefunction (C &, C &)을 가지고 있다면? (나는 실제로 가지고있는 간단한 예제를 게시하고 싶었다) – George

+0

'somefunction (C &, C &)'는'somefunction (B &, B &)'와 충돌하지 않는다. 모든 캐스트는 함수를 사용해야하는 모호성을 제거합니다. –

+0

하지만 함수를 호출하려면 B 또는 C 유형인지 확인해야합니다. 나는 어떤 수표 나 형형색환도 피하고 싶다. – George

0

예 그러나 C++은 런타임에 컴파일 타임에 사용할 함수를 결정했습니다. 그리고 컴파일시에 컴파일러가 보는 유일한 것은 (A &하는 &) - 그것은 사람들은 B.

의 경우 실제로 당신은 더 많은 코드를 작성해야
+0

글쎄 컴파일러는 그런 것들에 늦은 바인딩을 사용해야합니까? – George

+0

'늦은 바인딩'은 코드를 연결하는 데 사용됩니다. 이 경우에는 관련이 없습니다. –

+1

@ 조지 : 아니요. 그것은 * 할 수 * 런타임에 인수 형식을 검사하고 호출 할 함수를 결정하십시오. 그러나 그것은 언어가 정의 된 방식이 아니기 때문에 그렇게하지 않을 것입니다. :) – jalf

0

가 ....
& A1 무엇입니까 있음을 알 수 없다 = ...
& a2 = ...

포인터를 사용하면 안됩니까? a1과 a2를 유형 A로 저장하는 경우에도 B의 경우에도 A 오버로드가 호출됩니다. 동적으로 방송해야합니다.

void somefunction((B&)a1, (B&)a2); 

이 문제가되는 이유는 프로그램 설계가 아닌 언어이다 : 컴파일러가 선택할 수있는 하나 알 수 있도록

+0

나는 주조하는 것을 피하고 싶습니다. 코드가 필요 없으며 100 % 확실한 B 인스턴스입니다. – George

0

이 경우 컴파일러는 항상 somefunction(A&, A&);을 호출합니다. 왜 전화가 somefunction(B&, B&);일까요?

0

당신은 자신이 B라는 것을 확신한다고 말했습니다.

그런 경우라면 다음과 같이하십시오.

Ba1(); B a2();

A가 필요하다면 (A *) & B를 할 수 있습니다. 이것은 암시 적 캐스트이며 컴파일 할 때 발생합니다.

0

컴파일러가 가장 적합한 오버로드라고 생각하는 것을 선택했습니다. a1과 a2는 둘 다 클래스 A에 대한 참조로 선언되므로 클래스 B로 변환하기 위해 암시 적 캐스트가 필요하기 때문에 클래스 A에 대한 참조를 다른 클래스에 비해 "더 나은"오버로드에 맞 춥니 다.

암시 적으로 이러한 방식으로 업 캐스팅 할 수는 없습니다. 기본 클래스의 인스턴스 (이 경우 A)에 대한 포인터 또는 참조가 있으면 일반적으로 기본 클래스의 모든 인스턴스가 파생 클래스의 인스턴스가 아니기 때문에 암시 적으로 파생 클래스로 변환 할 수 없습니다 (예 : 모든 Bs는 As이지만, 모두는 Bs가 아닙니다).

당신은 함수를 호출하기 전에 B의 인스턴스로 선언해야합니다

다른 사람이 이미 언급 한 바와 같이
B& b1 = ... 
B& b2 = ... 
somefunction(b1, b2); 
2

는, 컴파일러는 올바른 오버로드를 선택합니다 - 그것 어떻게 언어 작품.

인스턴스의 유형을 확실히 알고 있다면 그냥 전송해야합니다. 그렇지 않은 경우는 실행시 주변의 매뉴얼 유형 검사를 얻을 수있는 한 가지 방법은 double dispatch은 다음과 같습니다

struct A; 
struct B; 

struct Base { 
    virtual perform(Base& b) = 0; 
    virtual perform(A& a) = 0; 
    virtual perform(B& b) = 0; 
}; 

struct A : Base { 
    virtual perform(Base& b) { b.perform(*this);  } 
    virtual perform(A& a) { someFunction(a, *this); } 
    virtual perform(B& b) { someFunction(b, *this); } 
}; 

struct B : A { 
    virtual perform(Base& b) { b.perform(*this);  } 
    virtual perform(A& a) { someFunction(a, *this); } 
    virtual perform(B& b) { someFunction(b, *this); } 
}; 

// ... 
Base& b1 = foo1(); 
Base& b2 = foo2(); 
b1.perform(b2); 
정확히 당신이 뭘 하려는지
1

? 그것은 두 개의 객체가 주어진 무언가를 수행하는 함수를 작성하려고 시도하는 것처럼 보입니다. 객체의 조합 유형에 따라 다른 것을 수행하기를 원하십니까?

정상적인 다형성이라 할지라도 내부적으로 "검사"한다는 것을 기억하십시오.

이 흥미로운 문제 상점입니다,

다형성은 당신에게 쉽게 하나의 객체가 아닌 두 가지의 유형에 따라 함수의 기능을 오버로드 할 수있는 기능을 제공합니다.

정확히 무엇을하려고합니까?

class Base 
{ 
    virtual SomeComonInterfaceObject DoMySpecialSomething() = 0; 
} 

void _doSomething(SomeComonInterfaceObject a, SomeComonInterfaceObject b); 

void doSomething(Base& o1, Base& o2) 
{ 
    _doSomething(o1->DoMySpecialSomething(), o2->DoMySpecialSomething()); 
} 

그 적합하지 않는 경우, 당신은 아마 단지를 확인할 수 있습니다 각 개체는 별도로 고유의 물건을 수행하고 공통 처리를위한 공통 객체를 반환 할 수 있도록 최선 제안은 그것을 만들 것 그것을 기반으로 세부 사항을 입력하십시오.

정상적인 다형성조차도 성능에 대해 걱정할 경우 "확인"을하지만 다른 언어도 필요하다는 점에 유의하십시오.

템플릿을 사용하는 것만으로도 문제를 해결할 수있는 유일한 방법은 실제로보기 흉하게 들릴 것입니다.

당신이 무엇을하려고하는지 아는 것이 재미있을 것입니다. 또한, 이러한 doSomething 함수는 두 매개 변수가 항상 같은 유형입니까? 또는 그들은 섞고 일치합니까?

A* a = new B; 
a->foo(); //calls B::foo (as long as foo is virtual) 

호출 할 수있는 기능을 기반으로 실행 시간에 해결되지 :

+0

그게 전부지만 그건 당신이 말했듯이 저에게 적합하지 않습니다 : ( 매개 변수가 항상 같은 유형이 아니며, 당신이 말한 것처럼 그들이 섞여서 일치 할 수 있습니다. 무엇을하려고하는 것은 일부 조합에서만 특정 작업을하는 것입니다 (& Base, & Base)를 사용하여 가장 일반적인 함수에서 "catch"하고 오류 처리를 수행합니다. – George

3

전화 기능 만 의 종류에이 객체를 기반으로, 가상 메소드 실행시에 결정된다 함수의 인수의 "실제"유형.

A* a = new B; 
X* x = new Y; 
a->foo(x); //assuming virtual and two overloads, calls B::foo(X*), not B::foo(Y*) 

는 일부 게시물 표시로 패턴을 수동으로 구현 될 수 있지만 더 내장 이중 파견 메커니즘, (동시에 두 개체의 동적 유형을 기반으로 호출 할 수있는 기능을 선택)이 없습니다.

당신은 당신이 항상 실제로 &을 B되며 캐스트를 원하지 않는 &는 A, 나는 종류가 될 것이라고 결론을 내릴 것을을 알고 있다고 말한다면 하드 코딩 당신은 수도 있으므로, 컴파일 타임에 대신 "컴파일 타임 다형성"을 시도하십시오. 지금

class A {}; 
class B {}; 

class C: public A {}; 

void somefunction(const A&, const A&); 
void somefunction(const B&, const B&); 

template <class T> 
void someotherfunction() 
{ 
    const T& a1 = T(); 
    const T& a2 = T(); 
    somefunction(a1, a2); 
} 

int main() 
{ 
    someotherfunction<A>(); 
    someotherfunction<B>(); 

    //combine with inheritance and it will still be 
    //possible to call somefunction(A&, A&) since 
    //somefunction(C&, C&) is not defined 
    someotherfunction<C>(); 
} 

A1 (여기에서, A와 B도.만큼 그들은 적절한 인터페이스를 가지고 관련 될 필요가 없다) 및 A2는 정말는 하나 개의 인스턴스에 따른다 및 BS 것 다른 경우, 과부하를 선택하는 한. (그렇지 않으면 const가 아닌 참조에 바인딩하는 것을 만드는 것이 더 힘들어지기 때문에 몇 가지 const를 추가했습니다.)

+0

컴파일 할 때 유형을 하드 코딩하지 않아도됩니다.하지만 도움을 주신 것에 감사드립니다. – George

+0

I didn 엄격하게 하드 코딩 된 것을 의미하지 만 템플릿을 사용하여 컴파일 타임에 생성됩니다. 유형이 런타임에만 결정된 경우에는 캐스트가 항상 정확하다고 자신있게 말할 수있는 방법이 없습니다. 매우 큰 아키텍처 변화를 의미하기 때문에 더 잘 알 것입니다. 상속은 모든 유형의 문제에 대한 마법의 치료법이 아닙니다 (저는 여러분의 문제가 부분적으로 이유라고 생각합니다. 왜 STL이 구현되지 않았는가? 런타임 다형성으로 테드). – visitor

0

내가 원하는 것은 파생 테이블을 사용하여 얻는 것입니다. 1 차원 대신 2 또는 3 차원 (아마 2) 일 수 있습니다. 도와 줘서 고마워!