Возврат абстрактного типа в базовый класс - PullRequest
10 голосов
/ 28 октября 2011

В проекте иерархии классов я использую абстрактный базовый класс, который объявляет различные методы, которые будут реализованы производными классами. В некотором смысле, базовый класс настолько близок к интерфейсу, насколько вы можете получить в C ++. Однако есть конкретная проблема. Рассмотрим код ниже, который объявляет наш интерфейсный класс:

class Interface {
public:
    virtual Interface method() = 0;
};

class Implementation : public Interface {
public:
    virtual Implementation method() { /* ... */ }
};

Конечно, это не скомпилируется, потому что вы не можете вернуть абстрактный класс в C ++. Чтобы обойти эту проблему, я использую следующее решение:

template <class T>
class Interface {
public:
    virtual T method() = 0;
};

class Implementation : public Interface<Implementation> {
public:
    virtual Implementation method() { /* ... */ }
};

Это решение работает, и все хорошо, но мне кажется, оно выглядит не очень элегантно из-за избыточного текста, который будет параметром для интерфейса. Я был бы рад, если бы вы, ребята, могли указать на любые другие технические проблемы, связанные с этим дизайном, но на данный момент это моя единственная проблема.

Есть ли способ избавиться от этого избыточного параметра шаблона? Возможно использование макросов?

Примечание: Рассматриваемый метод должен возвращать экземпляр. Мне известно, что если бы method() вернул указатель или ссылку, проблем не было бы.

Ответы [ 2 ]

5 голосов
/ 28 октября 2011

Хотя вы не можете вернуть по значению по понятным причинам, вполне нормально возвращать указатели или ссылки - это называется «ковариантный тип возврата», и это допустимая форма переопределения виртуальной функции.:

struct Base { virtual Base * foo(); }
struct Derived : Base { virtual Derived * foo(); }

Дело в том, что Derived::foo() является подлинным переопределением , а не перегрузкой с скрытым основанием, поскольку Derived* является указателем на производный класс Base.То же самое относится и к ссылкам.

Другими словами, если у вас есть Base * p, и вы звоните p->foo(), вы всегда можете обработать результат как указатель на Base (но если у вас есть дополнительныеинформация, например, что ваш класс на самом деле Derived, тогда вы можете использовать эту информацию).

Противоположный порядок компоновки, то есть «типы контравариантных аргументов», не допускается как часть C ++.

5 голосов
/ 28 октября 2011

Interface::method() не может вернуть экземпляр Interface без использования указателя или ссылки.Для возврата не указателя, не ссылающегося на экземпляр Interface требуется создание экземпляра самого Interface, что недопустимо, поскольку Interface является абстрактным.Если вы хотите, чтобы базовый класс возвращал экземпляр объекта, вы должны использовать одно из следующего:

Указатель:

class Interface
{
public:
  virtual Interface* method() = 0;
};

class Implementation : public Interface
{
public:
  virtual Interface* method() { /* ... */ }
};

Ссылка:

class Interface
{
public:
  virtual Interface& method() = 0;
};

class Implementation : public Interface
{
public:
  virtual Interface& method() { /* ... */ }
};

Параметр шаблона:

template<type T>
class Interface
{
public:
  virtual T method() = 0;
};

class Implementation : public Interface<Implementation>
{
public:
  virtual Implementation method() { /* ... */ }
};
...