简体   繁体   中英

C++ Abstract class can't have a method with a parameter of that class

I created this .h file

#pragma once

namespace Core
{
    class IComparableObject
    {
    public:
            virtual int CompareTo(IComparableObject obj)=0;
    };
}

But compiler doesn't like IComparableObject obj param if the method is virtual pure, while

virtual int CompareTo(IComparableObject obj) {}

It's ok, however I want it as virtual pure. How can I manage to do it? Is it possible?

You are trying to pass obj by value. You cannot pass an abstract class instance by value, because no abstract class can ever be instantiated (directly). To do what you want, you have to pass obj by reference, for example like so:

virtual int CompareTo(IComparableObject const &obj)=0;

It works when you give an implementation for CompareTo because then the class is not abstract any longer. But be aware that slicing occurs! You don't want to pass obj by value.

Well I have to give an unexpected answer here! Dennycrane said you can do this:

virtual int CompareTo(IComparableObject const &obj)=0;

but this is not correct either. Oh yes, it compiles, but it is useless because it can never be implemented correctly.

This issue is fundamental to the collapse of (statically typed) Object Orientation, so it is vital that programmers using OO recognize the issue. The problem has a name, it is called the covariance problem and it destroys OO utterly as a general programming paradigm; that is, a way of representing and independently implementing general abstractions.

This explanation will be a bit long and sloppy so bear with me and try to read between the lines.

First, an abstract class with a pure virtual method taking no arguments can be easily implemented in any derived class, since the method has access to the non-static data variables of the derived class via the this pointer. The this pointer has the type of a pointer to the derived class, and so we can say it varies along with the class, in fact it is covariant with the derived class type.

Let me call this kind of polymorphism first order, it clearly supports dispatching predicates on the object type. Indeed, the return type of such a method may also vary down with the object and class type, that is, the return type is covariant .

Now, I will generalise the idea of a method with no arguments to allow arbitrary scalar arguments (such as ints) claiming this changes nothing: this is merely a family of methods indexed by the scalar type. The important property here is that the scalar type is closed. In a derived class exactly the same scalar type must be used. in other words, the type is invariant .

General introduction of invariant parameters to a virtual function still permits polymorphism, but the result is still first order.

Unfortunately, such functions have limited utility, although they are very useful when the abstraction is only first order: a good example is device drivers.

But what if you want to model something which is actually interesting, that is, it is at least a relation?

The answer to this is: you cannot do it. This is a mathematical fact and has nothing to do with the programming language involved. Lets suppose you have an abstraction for say, numbers, and you want to add one number to another number, or compare them (as in the OP's example). Ignoring symmetry, if you have N implementations, you will have to write N^2 functions to perform the operations. If you add a new implementation of the abstraction, you have to write N+1 new functions.

Now, I have the first proof that OO is screwed: you cannot fit N^2 methods into a virtual dispatch schema because such a schema is linear. N classes gives you N methods you can implement and for N>1, N^2 > N, so OO is screwed, QED.

In a C++ context you can see the problem: consider :

struct MyComparable : IComparableObject {
  int CompareTo(IComparableObject &other) { .. }
};

Arggg! We're screwed! We can't fill in the .. part here because we only have a reference to an abstraction, which has no data in it to compare to. Of course this must be the case, because there are an open/indeterminate/infinite number of possible implementations. There's no possible way to write a single comparison routine as an axiom.

Of course, if you have various property routines, or a common universal representation you can do it, but this does not count, because then the mapping to the universal representation is parameterless and thus the abstraction is only first order. For example if you have various integer representations and you add them by converting both to GNU gmp's data type mpz, then you are using two covariant projection functions and a single global non-polymorphic comparison or addition function.

This is not a counter example, it is a non-solution of the problem, which is to represent a relation or method which is covariant in at least two variables (at least self and other).

You may think you could solve this with:

struct MyComparable : IComparableObject {
  int CompareTo(MyComparable &other) { .. }
};

After all you can implement this interface because you know the representation of other now, since it is MyComparable.

Do not laugh at this solution, because it is exactly what Bertrand Meyer did in Eiffel, and it is what many people do in C++ with a small change to try to work around the fact it isn't type safe and doesn't actually override the base-class function:

struct MyComparable : IComparableObject {
  int CompareTo(IComparableObject &other) {
     try 
       MyComparable &sibling = dynamic_cast(other);
       ...
    catch (..) { return 0; }
  }
};

This isn't a solution. It says that two things aren't equal just because they have different representations. That does not meet the requirement, which is to compare two things in the abstract. Two numbers, for example, cannot fail to be equal just because the representation used is different: zero equals zero, even if one is an mpz and the other an int. Remember: the idea is to properly represent an abstraction, and that means the behaviour must depend only on the abstract value, not the details of a particular implementation.

Some people have tried double dispatch. Clearly, that cannot work either. There is no possible escape from the basic issue here: you cannot stuff a square into a line. virtual function dispatch is linear, second order problems are quadratic, so OO cannot represent second order problems.

Now I want to be very clear here that C++ and other statically typed OO languages are broken, not because they can't solve this problem, because it cannot be solved, and it isn't a problem: its a simple fact. The reason these languages and the OO paradigm in general are broken is because they promise to deliver general abstractions and then fail to do so. In the case of C++ this is the promise:

struct IComparableObject { virtual int CompareTo(IComparableObject obj)=0; };

and here is where the implicit contract is broken:

struct MyComparable : IComparableObject {
  int CompareTo(IComparableObject &other) { throw 00; }
};

because the implementation I gave there is effectively the only possible one.

Well before leaving, you may ask: What is the right way (TM) . The answer is: use functional programming. In C++ that means templates.

template<class T, class U> int compare(T,U);

So if you have N types to compare, and you actually compare all combinations, then yes indeed you have to provide N^2 specialisations. Which shows templates deliver on the promise, at least in this respect. It's a fact: you can't dispatch at run time over an open set of types if the function is variant in more than one parameter.

BTW: in case you aren't convinced by theory .. just go look at the ISO C++ Standard library and see how much virtual function polymorphism is used there, compared to functional programming with templates..

Finally please note carefully that I am not saying classes and such like are useless, I use virtual function polymorphism myself: I'm saying that this is limited to particular problems and not a general way to represent abstractions, and therefore not worthy of being called a paradigm.

When the CompareTo member function is pure virtual, IComparableObject is an abstract class.

You can't directly copy an object of an abstract class.

When you pass an object by value you're directly copying that object.

Instead of passing by value, you can pass by reference to const .

That is, formal argument type IComparableObject const& .


By the way, the function should probably be declared const so that it can be called on const object.

Also, instead of #pragma once , which is non-standard (but supported by most compilers), consider an ordinary include guard.

Also, when posting code that illustrates a problem, be sure to post exact code. In this case, there's a missing semicolon at the end, indicating manual typing of the code (and so that there could be other typos not so easily identified as such, but instead misidentified as part of your problem). Simply copy and paste real code .


Cheers & hth.,

From C++03, §10.4 3:

An abstract class shall not be used as a parameter type, as a function return type, or as the type of an explicit conversion. Pointers and references to an abstract class can be declared.

Passing obj as a const reference is allowed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM