2010-03-09 1 views
12

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

Я получаю «test.cpp: 22: ошибка: недопустимый тип возвращаемого значения для« виртуальной 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 * в качестве типов возврата в унаследованных классах (было бы интуитивно для меня, что есть способ)?

+2

Вам действительно нужен этот тип перепутывания? (назовем его связью может быть немного коротким) –

+0

Иногда это не проблема языка, это проблема с тем, как мы пытаемся его использовать. Если ваши две иерархии настолько тесно связаны, я думаю, что вам будет лучше с одной иерархией (слияние 'A' с' C' и 'B' с' D'), так как очень похоже, что они не могут работать без друг друга в любом случае , –

+0

Ну, у меня есть два типа классов: спецификация задачи и исполнитель, который фактически выполняет задачу (запускает несколько потоков). Сама спецификация должна быть фабрикой для задачи, и каждый исполнитель должен получить доступ к ее спецификации. И есть различные спецификации с их задачами. Таким образом, исполнитель обертывает спецификацию, поэтому соединение только сильное в одном направлении, в другом направлении это только заводской метод. – Searles

ответ

7

Я не знаю, как иметь прямо связанные ковариантные члены в 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 действует) ,

0

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

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

+1

Вы неправильно поняли. Ковариантные типы возврата разрешены *. Если клиент ожидает указатель 'A', но вы даете ему указатель' B', это находит, потому что все экземпляры 'B' * являются * экземплярами' A'. Проблема в этом случае заключается в том, что компилятор не может проверить при объявлении 'B :: outC', что новый тип возврата' D' является потомком исходного типа возврата 'C'. Если бы компилятор мог видеть полное определение 'D', это позволило бы новой подписи. Обратите внимание, что компилятор не жалуется на 'D :: outA', потому что он знает, что' C' является потомком 'A'. –

+1

В моем примере тип возвращаемого варианта совместного использования в классе D не является проблемой. – Searles

4

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

Существует аналогичная проблема с попыткой реализовать ковариантный метод clone() с использованием шаблонов, which I found can be solved, но аналогичное решение все еще не работает здесь, потому что циклическая ссылка остается невозможной для решения.

+0

Это действительно проблема круговой зависимости. То же самое применяется при попытке использовать кросс-зависимые typedefs, перечисления, поля и т. Д. +1 – doc

+0

Все были укушены этой проблемой 'clone' ... Что мне не нравится в решении, это придало ему часть упаковки, 'typedef' не может быть объявлен переадресацией, и это боль, если ваши клиенты должны знать, чтобы не использовать неразделенное имя: / –

Смежные вопросы