C++:Как я могу избежать “недопустимого ковариантного возвращаемого типа” в унаследованных классах без приведения?
-
18-09-2019 - |
Вопрос
У меня есть довольно сложная иерархия классов, в которой классы перекрестно зависят друг от друга:Существуют два абстрактных класса A и C, содержащих метод, который возвращает экземпляр C и A соответственно.В их унаследованных классах я хочу использовать тип co-variant, что в данном случае является проблемой, поскольку я не знаю способа переадресации отношения наследования ship.
Я получаю "test.cpp:22:ошибка:недопустимый ковариантный возвращаемый тип для ‘virtual 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 * в качестве возвращаемых типов в унаследованных классах (мне было бы интуитивно понятно, что такой способ есть)?
Решение
Я не знаю ни одного способа иметь непосредственно связанные ковариантные члены в C ++.Вам придется либо добавить слой, либо самостоятельно реализовать ковариантный возврат.
Для первого варианта
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).
Другие советы
Насколько я знаю, нет никакого способа сделать это без явного приведения.Проблема в том, что определение класса B
не могу этого знать D
является подклассом C
пока он не увидит полное определение класса D
, но определение класса D
не могу этого знать B
является подклассом A
пока он не увидит полное определение класса B
, и таким образом, у вас есть циклическая зависимость.Это не может быть решено с помощью прямых объявлений, потому что прямое объявление, к сожалению, не может указать отношение наследования.
Аналогичная проблема возникает при попытке реализовать ковариантный clone()
метод с использованием шаблонов, который, как я обнаружил, может быть решен, но аналогичное решение здесь по-прежнему терпит неудачу, потому что циклическую ссылку по-прежнему невозможно разрешить.
Вы не можете этого сделать из-за ожиданий на стороне клиента.При использовании экземпляра C вы не можете определить, какой это тип C (D или что-то еще).Таким образом, если вы сохраните указатель B (полученный в результате вызова производного класса, но вы не знаете его во время компиляции) в указатель A, я не уверен, что все данные о памяти будут правильными.
Когда вы вызываете метод для полиморфного типа, среда выполнения должна проверить динамический тип объекта и переместить указатели в соответствии с вашей иерархией классов.Я не уверен, что вам следует полагаться на ковариацию.Взгляните на это