문제
동일한 유형의 두 객체를 비교할 때 동일한 클래스의 다른 인스턴스를 취하는 비교 함수를 갖는 것이 좋습니다. 기본 클래스에서이를 가상 함수로 구현하면 기능의 서명은 파생 클래스에서도 기본 클래스를 참조해야합니다. 이것을 다루는 우아한 방법은 무엇입니까? 비교가 가상이어야합니까?
class A
{
A();
~A();
virtual int Compare(A Other);
}
class B: A
{
B();
~B();
int Compare(A Other);
}
class C: A
{
C();
~C();
int Compare(A Other);
}
해결책
A, B 및 C의 의도 된 의미와 Compare ()의 의미론에 따라 다릅니다. 비교는 반드시 하나의 정확한 의미 (또는 그 문제에 대해 어떤 의미도)를 갖지는 않는 추상 개념입니다. 이 질문에 대한 단일 정답은 없습니다.
동일한 클래스 계층 구조를 가진 두 가지 완전히 다른 두 가지를 비교하는 두 가지 시나리오는 다음과 같습니다.
class Object
{
virtual int compare(const Object& ) = 0;
float volume;
};
class Animal : Object
{
virtual int compare(const Object& );
float age;
};
class Zebra : Animal
{
int compare(const Object& );
};
우리는 두 개의 얼룩말을 비교하는 두 가지 방법을 고려할 수 있습니다. 나이가 많고 볼륨이 더 많습니까? 두 비교는 유효하고 쉽게 계산할 수 있습니다. 차이점은 양을 사용하여 얼룩말을 다른 물체와 비교할 수 있지만 나이를 사용하여 얼룩말을 다른 동물과 비교할 수 있다는 것입니다. 비교 ()가 연령 비교 의미론은 기본 클래스 수준 (부피를 비교할 때의 대상이든, 나이를 비교할 때 동물이든)에서 정의되므로 이러한 시나리오 중 어느 것도 캐스팅을 요구하지 않는다는 점은 주목할 가치가 있습니다.
이는 일부 클래스가 단일 Catch-All Compare () 함수에 적합하지 않다는 더 중요한 문제를 제기합니다. compare_age () 및 compare_volume ()과 같이 비교중인 내용을 명시 적으로 명시하는 여러 기능을 구현하는 것이 더 합리적입니다. 이러한 기능의 정의는 의미론이 관련이있는 상속 계층의 시점에서 발생할 수 있으며, 아동 계급에 적응하는 것은 사소한 일이어야합니다 (전혀 조정이 필요하다면). Compare () 또는 Operator == ()를 사용한 간단한 비교 종종 올바른 시맨틱 구현이 명백하고 모호하지 않은 간단한 클래스에서만 적합합니다.
짧은 이야기 ... "그것은 의존한다".
다른 팁
나는 다음과 같이 구현할 것입니다.
class A{
int a;
public:
virtual int Compare(A *other);
};
class B : A{
int b;
public:
/*override*/ int Compare(A *other);
};
int A::Compare(A *other){
if(!other)
return 1; /* let's just say that non-null > null */
if(a > other->a)
return 1;
if(a < other->a)
return -1;
return 0;
}
int B::Compare(A *other){
int cmp = A::Compare(other);
if(cmp)
return cmp;
B *b_other = dynamic_cast<B*>(other);
if(!b_other)
throw "Must be a B object";
if(b > b_other->b)
return 1;
if(b < b_other->b)
return -1;
return 0;
}
이것은와 매우 유사합니다 IComparable
.NET의 패턴, 매우 잘 작동합니다.
편집하다:
위의 경고 중 하나는 그 것입니다 a.Compare(b)
(어디 a
A와 b
B) 평등을 반환 할 수 있으며 절대 예외를 던지십시오 b.Compare(a)
할 것이다. 때때로 이것은 당신이 원하는 것이며 때로는 그렇지 않습니다. 그렇지 않다면 아마 당신은 아마 당신의 Compare
가상으로 기능하거나 비교하고 싶습니다. type_info
기지에서 s Compare
에서와 같이 기능 :
int A::Compare(A *other){
if(!other)
return 1; /* let's just say that non-null > null */
if(typeid(this) != typeid(other))
throw "Must be the same type";
if(a > other->a)
return 1;
if(a < other->a)
return -1;
return 0;
}
파생 수업 ' Compare
기능은 기본 클래스를 호출해야하므로 변경할 필요가 없습니다. Compare
, 어디에 type_info
비교가 발생합니다. 그러나 교체 할 수 있습니다 dynamic_cast
재정의에서 Compare
a static_cast
.
아마도, 나는 이것을 그렇게 할 것입니다 :
class A
{
public:
virtual int Compare (const A& rhs) const
{
// do some comparisons
}
};
class B
{
public:
virtual int Compare (const A& rhs) const
{
try
{
B& b = dynamic_cast<A&>(rhs)
if (A::Compare(b) == /* equal */)
{
// do some comparisons
}
else
return /* not equal */;
}
catch (std::bad_cast&)
{
return /* non-equal */
}
}
};
비교는 반사적이어야하므로 :
let a = new A
let b = new B (inherits from A)
if (a.equals(b))
then b.equals(a) must be true!
그래서 a.equals(b)
B는 아마도 A가없는 필드를 포함하기 때문에 False를 반환해야합니다. b.equals(a)
아마도 거짓 일 것입니다.
따라서 C ++에서 비교는 가상이어야하며, 유형 검사를 사용하여 매개 변수가 현재 객체와 "동일"유형인지 확인해야합니다.
클래스 B 또는 C의 비교 ()가 항상 클래스 B 또는 C의 객체를 전달해야한다는 것을 의미한다면, 서명이 말하면 인스턴스 대신 인스턴스로 포인터로 작업하고 포인터를 다운 캐스트 할 수 있습니다. 방법을 사용하는 방법의 코드
int B::Compare(A *ptr)
{
other = dynamic_cast <B*> (ptr);
if(other)
... // Ok, it was a pointer to B
}
(이러한 과부하는 부모의 상태에 비교에 영향을 미치는 무언가를 추가하는 파생 클래스에만 필요합니다.)
C ++ 에서이 문제가 거의 없습니다. Java와 달리 동일한 루트 객체 클래스에서 모든 클래스를 상속받을 필요는 없습니다. 비교할 수있는 (/value semantics) 클래스를 다룰 때, 그것들이 다형성 계층 구조에서 나올 가능성은 거의 없습니다.
특정 상황에서 필요가 실제라면, 당신은 더블 디스패치/멀티 메트로 드 문제로 돌아갑니다. 이를 해결하는 다양한 방법이 있습니다 (Dynamic_cast, 가능한 상호 작용을위한 기능 테이블, 방문자 등).
Dynamic_cast 외에도 참조 또는 포인터, 아마도 const를 전달해야합니다. 비교 함수는 아마도 const 일 수도 있습니다.
class B: public A
{
B();
virtual ~B();
virtual int Compare(const A &Other) const;
};
int B::Compare(const A &Other) const
{
const B *other = dynamic_cast <const B*> (&Other);
if(other) {
// compare
}
else {
return 0;
}
}
편집 : 게시하기 전에 컴파일해야합니다 ...
나는 그것을 가상으로 만들지 말 것을 제안합니다. 유일한 단점은 클래스가 동일하지 않은 경우 사용과 비교하는 것을 명시해야한다는 것입니다. 그러나해야하기 때문에 오류 (컴파일 시간에)를 발견하여 런타임 오류가 발생할 수 있습니다 ...
class A
{
public:
A(){};
int Compare(A const & Other) {cout << "A::Compare()" << endl; return 0;};
};
class B: public A
{
public:
B(){};
int Compare(B const & Other) {cout << "B::Compare()" << endl; return 0;};
};
class C: public A
{
public:
C(){};
int Compare(C const & Other) {cout << "C::Compare()" << endl; return 0;};
};
int main(int argc, char* argv[])
{
A a1;
B b1, b2;
C c1;
a1.Compare(b1); // A::Compare()
b1.A::Compare(a1); // A::Compare()
b1.Compare(b2); // B::Compare()
c1.A::Compare(b1); // A::Compare()
return 0;
}