Возвращение абстрактного типа в базовый класс

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

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

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

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

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

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

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

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

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

Ответ 1

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() { /* ... */ }
};

Ответ 2

Пока вы не можете возвратиться по значению по понятным причинам, полностью ОК для возврата указателей или ссылок - это называется "ковариантный тип возврата", и это действительная форма переопределения виртуальной функции:

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

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

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

Противоположный порядок композиции, т.е. "контравариантные типы аргументов", не разрешен как часть С++.