Abstract class and operator! = In C ++

I have a problem with the implementation of the! = Operator in a given class derived from abstatic. The code is as follows:

class Abstract { public: //to make the syntax easier let use a raw pointer virtual bool operator!=(const Abstract* other) = 0; }; class Implementation { SomeObject impl_; //that already implement the operator!= public: bool operator!=(const Abstract* other) { return dynamic_cast<Implementation*>(other)->impl_ != this->impl_; } }; 

This code works, but it has the disadvantage of using dynamic_cast, and I need to handle errors during the casting operation.

This is a common problem that occurs when a function of a particular class tries to use some internal information (not available at the abstract class level) to complete the task.

Is there a better way to solve this problem?

Greetings

+6
c ++
source share
10 answers

You do not want to embed the equality operators, == or != , In the base class. The base class does not know how many or contents of descendants.

For example, using an example of the Shape class:

 struct Shape { virtual bool equal_to(const Shape& s) const = 0; // Makes Shape an abstract base class. bool operator==(const Shape& s) const { return equal_to(s); } bool operator!=(const Shape& s) const { return !equal_to(s); } }; struct Square : public Shape { bool equal_to(const Shape& s) const; }; struct Circle : public Shape { bool equal_to(const Shape& s) const; }; struct Flower : public Shape { bool equal_to(const Shape& s) const; }; struct Cloud : public Shape { bool equal_to(const Shape& s) const; }; 

To satisfy the equality operators of the Shape class, each child must implement the equal_to method. But wait, how does Square know which type of other Shape ?

In this example, the Square class should use dynamic_cast to refer to the Square object. This will not work if the argument is Circle , Flower , Cloud or some other undefined form.

The following is the correct concept that you must follow:

 Square my_square; Cloud my_cloud; Shape * p_shape_1 = &my_square; // Square is-a Shape, so this is legal. Shape * p_shape_2 = &my_cloud; // Cloud inherits from Shape, so this is legal. if (*p_shape_1 == *p_shape_2) // Legal syntax because of Shape::operator==(). { //???} 

The comparison above causes nasty behavior. This can happen in common functions that only work on shapes.

Decision

Change the design . You should never set up a public comparison operator that compares with the base class in the base class. Nastya.

Descendants are compared with descendants. Period. Squares to squares, flowers to flowers, and circles to circles. Implementation of comparison operators in descendant classes.

Comparing the contents of a base class: If you have common content in a base class, use the protected method to compare only the methods of the base class:

 struct Shape { protected: bool equal_shape_content(const Shape& s) const; }; 

This will make your program more reliable as it only compares the contents of the Shape with another Shape. That is all you can guarantee. See also "Slicing a base class."

+4
source share

Or, if this is not possible (for example, since SomeObject is not known when the Abstact class is defined), you will have to fully process it.

 class Abstract { public: //to make the syntax easier let use a raw pointer virtual bool operator!=(const Abstract* other) = 0; }; class Implementation : public Abstract { SomeObject impl_; //that already implement the operator!= public: bool operator!=(const Abstract* other) { // remember that dynamic cast can't remove a 'const' qualifier const Implementation* o = dynamic_cast<const Implementation*>(other); return (o == NULL || o->impl_ != this->impl_); } }; 
+2
source share

What you have in your question will not work. Writing a simple test application below gives the same result on every compiler that I currently have (MSVC, GCC):

 class Abstract { public: virtual bool operator!=(const Abstract* rhs) = 0; }; class Implementation : public Abstract { public: virtual bool operator!=(const Abstract* rhs) { std::cout << "Implementation::operator!= called" << std::endl; return false; } }; int main() { Abstract* pA = new Implementation; Abstract* pB = new Implementation; if (pA != pB) { std::cout << "Compared the pointers" << std::endl; } return 0; } 

Output:

 Compared the pointers 

You cannot overload operator! = Compare 2 pointers this way. You can do the following:

 bool operator!=(const Abstract* lhs, const Abstract* rhs) { return lhs->SomeVirtualFunctionThatReturnsUsefulInformation() != rhs->SomeVirtualFunctionThatReturnsUsefulInformation() } 

But I highly recommend against it, as it makes it impossible to compare your pointers (actual value of pointers).

Instead, you would be better off using this approach:

 class Abstract { public: virtual bool Equals(const Abstract* rhs) = 0; }; class Implementation : public Abstract { public: bool operator==(const Implementation& rhs) { return this->SomeFunctionThatReturnsUsefulInformation() == rhs.SomeFunctionThatReturnsUsefulInformation(); } virtual bool Equals(const Abstract* rhs) { Implementation* pRHS = dynamic_cast<Implementation*>(rhs); if (!pRHS) // not an Implementation of Abstract { return false; } return *this == *pRHS; } }; int main() { Abstract* pA = new Implementation; Abstract* pB = new Implementation; if (pA->Equals(pB)) { std::cout << "Works" << std::endl; } return 0; } 
+2
source share

How about adding a virtual implementation receiver to the Abstract class so you can just say

 return other->GetImp() != GetImp(); 
+1
source share

There are a lot of things in the example you pointed out, but in general you can use non-virtual overload, avoiding the cast:

 class Implementation { SomeObject impl_; //that already implement the operator!= public: virtual bool NotEqual(const Abstract* other) { Implementation *pImplementation=dynamic_cast<Implementation*>(other); return !pImplementation || other->impl_ != this->impl_; } bool NotEqual(const Implementation* other) { return other->impl_ != this->impl_; } }; 

Now, if you have Implementation * on the call site, an overloaded function will be called, and no promotion is needed.

+1
source share

You can either implement a virtual receiver at an abstract level:

 class Abstract { public: //to make the syntax easier let use a raw pointer virtual bool operator!=(const Abstract* other) = 0; private: virtual SomeObject& getImpl() = 0; }; class Implementation : public Abstract { SomeObject impl_; //that already implement the operator!= public: bool operator!=(const Abstract* other) { return other->getImpl() != this->getImpl(); } private: SomeObject& getImpl() { return impl_; } }; 
0
source share

I think the most practical alternative is just type checking before comparing:

 bool operator!=( const Abstract * other ) { if( other == NULL || typeid( *other ) != typeid( Implementation ) ) return true; return static_cast<Implementation *>( other )->_impl != _impl; } 
0
source share

When trying to do something related to two or more polymorphic objects, a virtual method on one and dynamic_cast on the other is usually your best option. There are other approaches, but they are necessarily complex.

I would also give the virtual method a different name than the operator. Reduces confusion, at least for me.

 class Abstract { public: virtual ~Abstract(); virtual bool equal_to(const Abstract&) const = 0; }; inline bool operator==(const Abstract& x, const Abstract& y) { return x.equal_to(y); } inline bool operator!=(const Abstract& x, const Abstract& y) { return !(x==y); } class Implementation { /*...*/ }; bool Implementation::equal_to(const Abstract& obj) const { const Implementation* der_obj = dynamic_cast<const Implementation*>(&obj); if (!der_obj) return false; return impl_ == der_obj->impl_; } 

Be sure to check if the result of dynamic_cast NULL. Presumably, there may be several implementation classes (if not now, in the future).

Also my above Implementation::equal_to may or may not be correct if there are any Implementation::equal_to subclasses.

0
source share

To implement your operator!= , You need some kind of double dispatch: you need to call a function depending on the type of both objects.

To avoid the problems you were talking about (always using dynamic_cast ), simply overload operator!= So that it accepts the Implementation object.


The solution that I usually choose is similar to what you suggested.

Any derived class' operator!= Checks to see if the type of the Abstract object is the same for the type of implementation if it distinguishes the Abstract object as Implementation and performs your checks.

I would suggest using type_info + static_cast instead of dynamic_cast , since the latter is slower.


Other ways to implement dual dispatching in C ++ are available, for example, using a visitor template (or many others), but I would not suggest it in this case, since they are more annoying to implement them and give other advantages.


A few notes:

  • You should pass links, why would you define a comparison operator between an Abstract object and a pointer?

  • This code does not work at all. For example, in your fragment, Implementation not inherited from Abstract , so you cannot use dynamic_cast .

0
source share

This can be done without dynamic_cast and may even work correctly, but it uses the "evil" of the base class, knowing all its subclasses. (A typical problem is also with the visitor pattern) using double submission.

You might be able to get around an external class that knows all the implementations, but does not have it in the actual base class.

With a simple model, I will have Abstract and Impl1 and Impl2. I will call the equals () method

 class Abstract { public: virtual ~Abstract() {} virtual bool equals( const Abstract * other ) const =0; virtual bool equals_Impl1( const Impl1 * other ) const { return false; } virtual bool equals_Impl2( const Impl2 * other ) const { return false; } }; class Impl1 : public Abstract { public: bool equals( const Abstract * other ) const { return other->equals_Impl1( this ); } bool equals_Impl1( const Impl1 * other ) const { // calculate and return true if they compare } }; // do the same with Impl2 

Not ideal because it is not "extensible", i.e. you cannot add Impl3 without changing Abstract to support it.

0
source share

All Articles