С++: Как я могу избежать "недопустимого типа возвращаемого ковариантного типа" в унаследованных классах без кастинга?

У меня довольно сложная иерархия классов, в которой классы являются кросс-подобными, в зависимости друг от друга. Существуют два абстрактных класса A и C, содержащие метод, который возвращает экземпляр C и A соответственно. В их унаследованных классах я хочу использовать тип совместного варианта, который в этом случае является проблемой, так как я не знаю способ переслать-объявить отношение отношения наследования.

Я получаю "test.cpp: 22: error: недопустимый тип возвращаемого ковариантного типа для" виртуальной D * B:: outC() "- ошибка, поскольку компилятор не знает, что D является подклассом C.

class C;

class A {
public:
        virtual C* outC() = 0;
};

class C {
public:
        virtual A* outA() = 0;
};


class D;

class B : public A {
public:
        D* outC();
};

class D : public C {
public:
        B* outA();
};

D* B::outC() {
        return new D();
}

B* D::outA() {
        return new B();
}

Если я изменил тип возврата B:: outC() на C *, то пример компилируется. Есть ли способ сохранить B * и D * в качестве типов возврата в унаследованных классах (было бы интуитивно для меня, что есть способ)?

Ответ 1

Я не знаю, как иметь прямо связанные ковариантные члены в С++. Вам нужно либо добавить слой, либо реализовать ковариантный возврат себя.

Для первого варианта

class C;

class A {
public:
        virtual C* outC() = 0;
};

class C {
public:
        virtual A* outA() = 0;
};


class BI : public A {
public:
};

class D : public C {
public:
        BI* outA();
};

class B: public BI {
public:
        D* outC();
};

D* B::outC() {
        return new D();
}

BI* D::outA() {
        return new B();
}

и для второго

class C;

class A {
public:
        C* outC() { return do_outC(); }
        virtual C* do_outC() = 0;
};

class C {
public:
        virtual A* outA() = 0;
};


class D;

class B : public A {
public:
        D* outC();
        virtual C* do_outC();
};

class D : public C {
public:
        B* outA();
};

D* B::outC() {
        return static_cast<D*>(do_outC());
}

C* B::do_outC() {
        return new D();
}

B* D::outA() {
        return new B();
}

Обратите внимание, что этот второй параметр - это то, что неявно выполняется компилятором (с некоторыми статическими проверками, которые действительны для static_cast).

Ответ 2

Насколько я знаю, нет никакого способа сделать это без явного литья. Проблема в том, что определение класса B не может знать, что D является подклассом C, пока не увидит полное определение класса D, но определение класса D не может знать что B является подклассом A, пока не увидит полное определение класса B, и поэтому у вас есть циклическая зависимость. Это невозможно решить с помощью forward-declarations, потому что в прямом объявлении, к сожалению, не может быть определено отношение наследования.

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

Ответ 3

Вы не можете сделать это из-за ожидания клиентской стороны. При использовании экземпляра C вы не можете определить, какой тип C (D или что-то еще). Таким образом, если вы храните указатель B (полученный в результате вызова производного класса, но вы не знаете его во время компиляции) в указатель A, я не уверен, что все материалы памяти будут правы.

Когда вы вызываете метод полиморфного типа, среда выполнения должна проверять динамический тип объекта и перемещает указатели в соответствии с вашей иерархией классов. Я не уверен, что вы должны полагаться на ковариацию. Посмотрите this