简体   繁体   中英

abstract methods and overiding function in C++ and Java

In C++ and Java, or their respecting rules, what limits are placed on overiding abstract methods. Must you match the arguments or return type. I usually see abstract functions implemented with only a return type and no arguments, is it up to derived class to specify the rest. How does it work exactly?

Method overriding must have the same method signature of the parent method it's overriding, otherwise it's not called overriding.

Java :

public abstract class AbstractTest {

    public abstract void test() throws Exception;
}

public class ConcreteTest extends AbstractTest {

    @Override
    public void test() throws Exception {

    }
}

As you can see, ConcreteTest (which extends AbstractTest ) must override test() . They have the same method name, return types and no method parameters. The subclass can omit the exceptions thrown from the base class and throw its own Exception. The subclass can also add additional (un)checked exception.

As Peter Lawrey mentioned, a java interface methods are implicitly abstract method (See my SO question on Java Abstract Interface ).

What is crucial here is that the method visibility cannot change in this case (as it's a hierarchical visibility, ie private->protected->public). This is valid though:

public abstract class AbstractTest {

    protected abstract void test() throws Exception;
}

public class ConcreteTest extends AbstractTest {

    @Override
    public void test() throws Exception {

    }
}

(The parent has a protected method and the subclass can override the same method and only has 2 choice for visibility: protected or public).

Also, Suppose you have

public class B {

}

public class D extends B {

}

public abstract class Base {

    public abstract B foo();
}

public class Derived extends Base {

    @Override
    public D foo() {
        // TODO Auto-generated method stub
        return new D();
    }

}

You will see that Derived returns a D and not a B . Why is that? That's because the derived class follows the same signature as the parent class and the return type of the derived class is a subtype of the return type of the parent class.

So, I can have this:

Base pureBase = new Derived();
B b = pureBase.foo(); //which returns class D

if (b instanceof D) {
   //sure, it is, do some other logic
}

In C++, you can get similar effect, using Covariant Return types

C++

class AbstractTest {
public:
    virtual void test() = 0;
};


class ConcreteTest : AbstractTest {
public:
    void test() {
        //Implementation here...
    }
};

In C++, a class with a pure virtual function (a virtual function that ends with a =0 ) is known as an Abstract class. The subclass (in C++, class extension is delimited by : ) override the pure virtual method (except it doesn't contain the =0 ). It has the same signature has its parent class.

Going back to our Java example, suppose you have:

class B {

};

class D : B {

};

class Base {
public:
    virtual B* foo() = 0;
}

class Derived : Base {
public:
    D* foo() {
        return new D();
    }
}

The same reasoning (as explained in java) is done here. Covariant return types also works with protected and private inheritance. More on Covariant return types .

I don't know about Java, but in C++ you have to specify the exact same argument types. The return type -on the other hand- is a covariant type, which means that if a pointer or reference to a type A is returned in the original function, an overriding function can return a pointer or reference to a type B as long as B is either A, or derives directly or indirectly from it.

As pointed by Als, a function must have been declared virtual in order to be overrided. Since the OP explicitly asked about abstract methods, which are defined both virtual and =0 , there should be no need to point this out. However, I want to make extra clear that the overriding function does not need to be declared virtual. As the cited standard says, a member function matching the signature (with relaxed rules, as for covariant types) of a base member function declared virtual will be an override regardless of whether it is specified virtual or not. That is, overriding functions do not need to be declared virtual; abstract member functions, on the other hand, must be.

Both languages are similar with respect to the requirements on overriding with the differences in semantics that are natural. Basically both require the exact same constraints on calling code (ie arguments) and offer the same or more strict guarantees on processing. This might sound a bit fuzzy here, but if you keep that in mind it is simple.

When is it an override

For a member function (method) to override a member of the base class both languages require the function to be polymorphic ( virtual in C++, not final in Java) have the same name and the same number an type of arguments. Some languages allow for contra-variant argument types, but neither Java nor C++ do.

Covariant return type

Covariant here means that the type of the return type changes in the same way that the types on which the member function is implemented. That is, the type returned by a derived function must be polymorphic and be the same or derived from the same type declared in the base class. Java is a reference language, and thus all return types can exhibit polymorphism except primitive types. C++ is a value language, and only references and pointers are polymorphic. That means that in Java the returned type must match exactly or be a reference type and be derived from the type returned by the base. In C++, it must be a reference or pointer to the same or a derived type. As in the introduction the reason is that if you call the member function through a base you will have an object that matches what you expect.

Exception specifications

Exception specifications are not very common in C++, but they are in Java. In both languages, though the approach to overriding is the same: the overriding method in the derived class must have tighter constraints as to what can be thrown. The differences in the language surface here, as Java only verifies checked exceptions, so it will allow unchecked exceptions in the derived types that were not thrown by the base. On the other hand, the deriving function cannot add new checked exceptions not present in the base class, again, covariance comes into play, and the derived function can throw covariant exceptions. In C++ the exception specifications have a complete different meaning, but in the same way, the specification in the derived type must be more constrained than in the base and it also allows covariant exception specifications.

The rationale is the same, if you write a try {} catch() {} block around a call through a reference to the base type, and it catches all of the exceptions that are declared in the base, a call to the override will have all the exceptions caught in the same blocks --except possibly unchecked exceptions in Java.

Access modifiers

In Java the access specification to the derived method must be at least as restrictive as that of the base, that is, if the base function declaration specifies protected , then the derived function cannot be public , but can on the other hand be private , interestingly Java does not allow you to override a private function in the base class.

In C++, access specifiers don't come into play for overriding, and you can modify the access specifiers as you wish, making it more or less restrictive in the derived classes. Incidentally, you can override a private member in the base class (that is declared virtual ), and that is commonly used to implement the NVI pattern (Non Virtual Interface), that has to be implemented through protected methods in Java.

Stop overriding

Java allows you to break the chain of overriding at any level, by marking the member function as final or alternatively making it private . In C++ (current standard) you cannot break the overriding chain at any point, not even in those cases where the final overrider does not have access to the member function it is overriding, which makes for a weird effect:

struct base {
   virtual void f() {}
};
struct derived : private base {
   void g() {
      f();
   }
};
struct most_derived : derived {
   void f() {                    // overrides base::f!!!
      //base::f();               // even if it does not have accesss to it
   } 
};

In that example, because inheritance is private at the derived level, most_derived does not have access to the base subobject, from its point of view, it does not derive from base (reason why base::f() will fail to compile inside most_derived::f() ) but on the other hand, by implementing a function with the signature void () it is providing an override for base::f . A call to g() on a most_derived object will be dispatched to most_derived::f() , while on a derived object will be dispatched to base::f() .

Java:

abstract class MyAbstract {
    abstract String sayHelloTo(String name);
}

final class SayEnglish extends MyAbstract {
    @Override
    public String sayHelloTo(String name) {
        return "Hello, " + name + "!";
    }
}

final class SayLatin extends MyAbstract {
    @Override
    public String sayHelloTo(String name) {
        return "Lorem, " + name + "!";
    }
}

The same is for C++ considering syntax difference, ie the same signature for overridden abstract method.

Your override methods in java should have the same signature as the abstract method you are overriding. Also you cannot restrict access more than the parent class. See http://download.oracle.com/javase/tutorial/java/IandI/override.html

I assume that you mean C++. The same as java the overriding methods signature should match the overridden. See http://www.learncpp.com/cpp-tutorial/126-pure-virtual-functions-abstract-base-classes-and-interface-classes/

Wiki has a page too en.wikipedia.org/wiki/Method_overriding. Abstract methods can have parameters. There is not restriction on that. In many cases it might not make sense to pass parameters. Hope this helps :)

The signature of the method ( the return type, the type and number of arguments ) should exactly match in the derived class to that of the base class. Else the derived class will also become be abstract.

Example:

struct foo{
    virtual void foobar( int myNum) = 0;
};

struct bar: foo{
    int foobar(int myNum ){}    
};

int main(){

    foo *obj = new bar();
    return 0;
}

test.cc:6: error: conflicting return type specified for 'virtual int bar::foobar(int)'
test.cc:2: error: overriding 'virtual void foo::foobar(int)'

As @Als mentioned, Covariant Return Type is an exception where the return type can be different. By different, I mean the different types should be type compatible to each. Pointer/reference of a derived class type in C++ is type compatible with pointer/reference of the base type.

Example from the link :

#include <iostream>

// Just create a class, and a subclass
class Foo {};
class Bar : public Foo {};

class Baz
{
  public:
  virtual Foo * create()
    {
      return new Foo();
    }
};

class Quux : public Baz
{
  public:
  // Different return type, but it's allowed by the standard since Bar
  // is derived from Foo
  virtual Bar * create()
    {
      return new Bar();
    }
};

int main()
{
  Quux *tmp = new Quux();
  Bar *bar = tmp->create();

  return 0;    
}

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