абстрактные методы и функции оверидинга в C ++ и Java - PullRequest
3 голосов
/ 15 сентября 2011

В C ++ и Java, или их соответствующих правилах, какие ограничения накладываются на множество абстрактных методов.Должны ли вы соответствовать аргументы или тип возвращаемого значения.Я обычно вижу абстрактные функции, реализованные только с возвращаемым типом и без аргументов, это зависит от производного класса, чтобы определить остальное.Как это работает точно?

Ответы [ 6 ]

4 голосов
/ 15 сентября 2011

Метод переопределения должен иметь такую ​​же сигнатуру метода родительского метода, который он переопределяет, в противном случае он не называется переопределением.

Java

public abstract class AbstractTest {

    public abstract void test() throws Exception;
}

public class ConcreteTest extends AbstractTest {

    @Override
    public void test() throws Exception {

    }
}

Как видите, ConcreteTest (что расширяет AbstractTest) должно переопределить test(). Они имеют одинаковое имя метода, возвращаемые типы и не имеют параметров метода. Подкласс может опустить исключения, выданные из базового класса, и выдать свое собственное исключение. Подкласс также может добавить дополнительное (не) проверенное исключение.

Как упомянул Питер Лоури , методы интерфейса java являются неявно абстрактным методом (см. Мой вопрос SO по Java Abstract Interface ).

Здесь важно то, что видимость метода не может измениться в этом случае (так как это иерархическая видимость, то есть private-> protected-> public). Это действительно так:

public abstract class AbstractTest {

    protected abstract void test() throws Exception;
}

public class ConcreteTest extends AbstractTest {

    @Override
    public void test() throws Exception {

    }
}

(Родитель имеет защищенный метод, и подкласс может переопределять тот же метод и имеет только 2 варианта для видимости: защищенный или открытый).

Кроме того, предположим, у вас есть

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();
    }

}

Вы увидите, что Derived возвращает D, а не B. Это почему? Это связано с тем, что производный класс следует той же сигнатуре , что и родительский класс, а тип возвращаемого значения производного класса - subtype возвращаемого типа родительского класса.

Итак, я могу иметь это:

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

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

В C ++ вы можете получить аналогичный эффект, используя Ковариантный тип возврата

C ++

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


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

В C ++ класс с чисто виртуальной функцией (виртуальная функция, оканчивающаяся на =0) известен как абстрактный класс. Подкласс (в C ++ расширение класса ограничено :) переопределяет чисто виртуальный метод (за исключением того, что он не содержит =0). У него такая же подпись, есть родительский класс.

Возвращаясь к нашему примеру с Java, предположим, что у вас есть:

class B {

};

class D : B {

};

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

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

То же рассуждение (как объяснено в Java) делается здесь. Ковариантный тип возврата также работает с защищенным и частным наследованием. Подробнее о Ковариантные типы возврата .

2 голосов
/ 15 сентября 2011

Я не знаю о Java, но в C ++ вы должны указывать точно такие же типы аргументов. Тип возврата - с другой стороны - является ковариантным типом, что означает, что если в исходной функции возвращается указатель или ссылка на тип A, переопределяющая функция может возвращать указатель или ссылку на тип B, если только B является либо А, либо прямо или косвенно происходит от него.

Как указал Алс, функция должна быть объявлена ​​виртуальной, чтобы ее можно было переопределить. Поскольку в OP явно задан вопрос об абстрактных методах, которые определены как virtual и =0, не нужно указывать на это. Однако я хочу прояснить, что переопределяющая функция не должна быть объявлена ​​ виртуальной. Как сказано в цитируемом стандарте, функция-член, соответствующая сигнатуре (с ослабленными правилами, как для ковариантных типов) базовой функции-члена, объявленной виртуальной, будет переопределена независимо от того, задана она виртуальной или нет. То есть переопределяющие функции не нужно объявлять виртуальными; абстрактные функции-члены, с другой стороны, должны быть.

1 голос
/ 15 сентября 2011

Оба языка схожи в отношении требований по переопределению с естественными различиями в семантике.В основном оба требуют одинаковых ограничений на вызывающий код (то есть аргументы) и предлагают одинаковые или более строгие гарантии при обработке.Это может звучать немного нечетко, но если учесть это, все просто.

Когда это переопределение

Для функции-члена (метода), чтобы переопределить член базового класса, оба языка требуют, чтобы функция была полиморфной (virtualв C ++, не final в Java) имеют одинаковые имена и одинаковые номера типа аргументов.Некоторые языки допускают противоположные типы аргументов, но ни Java, ни C ++ не делают.

Ковариантный тип возвращаемого значения

Ковариант здесь означает, что тип возвращаемого типа изменяется таким же способом , что и типы, в которых реализована функция-член.То есть тип, возвращаемый производной функцией, должен быть полиморфным и быть таким же или производным от того же типа, объявленного в базовом классе.Java является ссылочным языком, и, следовательно, все возвращаемые типы могут демонстрировать полиморфизм, кроме примитивных типов.C ++ является языком value , и только ссылки и указатели являются полиморфными.Это означает, что в Java возвращаемый тип должен точно соответствовать или быть ссылочным типом и быть производным от типа, возвращаемого базой.В C ++ это должен быть указатель или указатель на тот же или производный тип.Как и во введении, причина в том, что если вы вызовете функцию-член через базу, у вас будет объект, который соответствует тому, что вы ожидаете.

Спецификации исключений

Спецификации исключений не очень распространены в C ++, но в Java.Хотя в обоих языках подход к переопределению одинаков: метод переопределения в производном классе должен иметь более жесткие ограничения на то, что может быть выброшено.Различия в языковой поверхности здесь, поскольку Java только проверяет проверено исключений, поэтому это позволит непроверенные исключения в производных типах, которые не были выброшены базой.С другой стороны, производная функция не может добавлять новые проверенные исключения, отсутствующие в базовом классе, опять же, ковариация вступает в игру, и производная функция может генерировать ковариантные исключения.В C ++ спецификации исключений имеют совершенно другое значение, но точно так же спецификация в производном типе должна быть более ограниченной, чем в базовом, и она также допускает ковариантные спецификации исключений.

Основание такое жеЕсли вы пишете блок try {} catch() {} вокруг вызова через ссылку на базовый тип, и он перехватывает все исключения, объявленные в базе, при вызове переопределения все исключения будут перехвачены в одних и тех же блоках --все возможно непроверенные исключения в Java.

Модификаторы доступа

В Java спецификация доступа к производному методу должна быть как минимум такой же строгой, както из base, то есть, если объявление базовой функции задает protected, то производная функция не может быть public, но, с другой стороны, может быть private, интересно, что Java не позволяет переопределить private функция в базовом классе.

В C ++ спецификаторы доступа не вступают в игрудля переопределения, и вы можете изменять спецификаторы доступа по своему усмотрению, делая его более или менее ограничительным в производных классах.Кстати, вы можете переопределить член private в базовом классе (который объявлен virtual) и который обычно используется для реализации шаблона NVI (не виртуального интерфейса), который должен быть реализован с помощью методов protected вJava.

Прекратить переопределение

Java позволяет разорвать цепочку переопределения на любом уровне, пометив функцию-член как final или, альтернативно, сделав ее private. В C ++ (текущий стандарт) вы не можете разорвать переопределяющую цепочку ни в одной точке, даже в тех случаях, когда final overrider не имеет доступа к функции-члену, которую переопределяет, что создает странный эффект:

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
   } 
};

В этом примере, поскольку наследование является частным на уровне derived, most_derived не имеет доступа к подобъекту base, с его точки зрения, оно не происходит от base (причина, по которой base::f() не удастся скомпилировать внутри most_derived::f()), но, с другой стороны, реализуя функцию с подписью void (), она обеспечивает переопределение для base::f. Вызов g() для most_derived объекта будет отправлен на most_derived::f(), а для derived объект будет отправлен на base::f().

0 голосов
/ 15 сентября 2011

Сигнатура метода (тип возвращаемого значения, тип и количество аргументов) должна в точности соответствовать производному классу и базовому классу. В противном случае производный класс также станет абстрактным.

Пример:

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: ошибка: конфликтный тип возврата, указанный для «virtual int bar :: foobar (int)»
test.cc:2: ошибка: переопределение ‘virtual void foo :: foobar (int)’

Как уже упоминалось @Als, Ковариантный тип возвращаемого значения является исключением, где тип возвращаемого значения может быть другим. Я имею в виду, что разные типы должны быть совместимы по типу с каждым. Указатель / ссылка на производный тип класса в C ++ совместим по типу с указателем / ссылкой базового типа.

Пример по ссылке:

#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;    
}
0 голосов
/ 15 сентября 2011

Ваши методы переопределения в java должны иметь ту же сигнатуру, что и абстрактный метод, который вы переопределяете.Также вы не можете ограничить доступ больше, чем родительский класс.См. http://download.oracle.com/javase/tutorial/java/IandI/override.html

Я предполагаю, что вы имеете в виду C ++.Так же, как и в java, сигнатура переопределенных методов должна соответствовать переопределенной.См. http://www.learncpp.com/cpp-tutorial/126-pure-virtual-functions-abstract-base-classes-and-interface-classes/

В вики тоже есть страница en.wikipedia.org/wiki/Method_overriding.Абстрактные методы могут иметь параметры.На это нет ограничений.Во многих случаях может не иметь смысла передавать параметры.Надеюсь, это поможет:)

0 голосов
/ 15 сентября 2011

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 + "!";
    }
}

То же самое для C ++ с учетом разницы в синтаксисе, то есть той же сигнатуры для переопределенного абстрактного метода.

...