2010-07-05 4 views
2

Background: Я работаю над framework, который генерирует код на C++ на основе существующей модели класса Java. По этой причине я не могу изменить упомянутую ниже круговую зависимость.Шаблоны, круговые зависимости, методы, о, мой!

Дано: отношения класса

  • Родитель-Ребенок
  • Родитель содержит список детей
  • Пользователи должны иметь возможность посмотреть тип элемента списка во время выполнения

Я смоделировал это в следующей тестовой таблице:

Main.cpp

#include "Parent.h" 

#include <iostream> 
using std::cout; 
using std::endl; 

int main(int argc, char* argv[]) 
{ 
    Parent parent; 
    cout << Parent::getType() << endl; 
    cout << parent.getChildren().getType() << endl; 
    return 0; 
} 

Parent.h

#ifndef PARENT_H 
#define PARENT_H 

#include <string> 

#include "Array.h" 
class Child; 

class Parent 
{ 
public: 
    Array<Child> getChildren() 
    { 
     return Array<Child>(); 
    } 

    static std::string getType() 
    { 
     return "parent"; 
    } 
}; 

#endif 

Child.h

#ifndef CHILD_H 
#define CHILD_H 

#include "Parent.h" 

class Child: public Parent 
{ 
}; 

#endif 

Array.h

template <typename ElementType> 
class Array 
{ 
public: 
    static std::string getType() 
    { 
     return ElementType::getType(); 
    } 
}; 
  1. Когда я компилирую приведенный выше код я получаю: error C2027: use of undefined type 'Child' на return ElementType::getType();

  2. Если я пытаюсь #include "Child.h" вместо прямого заявления я получаю: error C2504: 'Parent' : base class undefined в class Child: public Parent

  3. Если я пытаюсь Array<Child*> вместо Array<Child> я получаю: error C2825: 'ElementType': must be a class or namespace when followed by '::' в return ElementType::getType();

Круговая зависимость возникает из-за:

  1. Child.h должен знать о классе Parent
  2. Parent.h нужно знать о классе массива
  3. Array.h должен знать о классе ребенка

Есть идеи?

+4

Родитель, который знает о его производных классов кажется мне недостатком дизайна. – Shirik

+0

Справедливо, но, как я уже объяснил, я ничего не могу сделать, чтобы изменить это. Я создаю код, основанный на проектах, которые существуют в дикой природе. – Gili

+1

Где находится код 'ElementType child;'? – rlbond

ответ

4

ошибка происходит из-за Дочерний класс не присутствует, когда шаблон создается.

Добавьте следующее либо Main или на конце из Parent.h:

#include "Child.h" 

Это нормально компилируется как с г ++ 4 и VS 2010.

+0

Как указывается в вопросе, вы получите сообщение об ошибке C2504. – Gili

+1

@Gili В этом вопросе речь идет о включении child.h перед определением родителя. Включая его * после *, определение решает проблему. –

+0

@Pete, Моя ошибка. Ты прав! – Gili

4

Один из способов решения этой проблемы - отделить реализации от интерфейсов.

Итак, поставьте реализацию Parent в файл .cpp, чтобы компилятор мог видеть определения родительского, дочернего и массива при компиляции Parent :: getChildren().

#ifndef PARENT_H 
#define PARENT_H 
#include <string> 
#include "Array.h" 

class Child; 

class Parent 
{ 
public: 
    Array<Child> getChildren(); 
    static std::string getType(); 
}; 

#endif 

И в материнской плате.каст:

#include "parent.hpp" 
#include "child.hpp" 

Array<Child> Parent::getChildren() { 
    return Array<Child>(); 
} 

// etc. 

Update:

Да, реальная проблема вызвана массиву :: GetType() будучи инстанцирован без определения Ребенок присутствует, так что мое решение является неполным.

Решение Пита Киркхэма хорошее: просто включите child.hpp в основной.

Для разделения интерфейса/реализации для работы потребуется отдельный файл реализации для массива с явным экземпляром массива и любыми другими необходимыми экземплярами. Вероятно, это не то, что вы хотите, но для полноты картины, это будет выглядеть примерно так:

В array.hpp:

#ifndef ARRAY_HPP 
#define ARRAY_HPP 

#include <string> 

template <typename ElementType> 
class Array 
{ 
public: 
    static std::string getType(); 
}; 

#endif 

И в array.cpp:

#include "array.hpp" 
#include "child.hpp" 

template<typename ElementType> 
std::string Array<ElementType>::getType() 
{ 
    return ElementType::getType(); 
} 

template class Array<Child>; 
+0

+1 для отдельной реализации из интерфейса. – 5ound

+0

@janm, я не думаю, что ваше решение применяется для этого вопроса. Компилятор жалуется на «return ElementType :: getType()», а не «Parent :: getChildren()», и первый не может попасть в файл cpp, потому что он использует шаблоны. – Gili

+0

@ Gili: Нет, это правильный путь. Сообщение об ошибке вызывается «proximally» с помощью 'return ElementType :: getType()', но основной причиной является 'Parent :: getChildren()' - *, что * является тем, что вызывает экземпляр 'Array ', что не работает потому что мы еще не видели определения «Ребенок». Вы должны подождать, пока не будет определен 'Child' до создания экземпляра' Array ', подразумевая, что определение' Parent :: getChildren() '* must * не должно быть включено. –

0

Может быть, вы могли бы вместо этого используйте указатель на Child in Parent? Что-то вроде

#ifndef PARENT_H 
#define PARENT_H 

#include <string> 

#include "Array.h" 
class Child; 

class Parent 
{ 
public: 
    Array<Child*> getChildren() 
    { 
     return Array<Child*>(); 
    } 

    static std::string getType() 
    { 
     return "parent"; 
    } 
}; 

#endif 

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

+0

Использование указателя на Child не помогает. Как выясняется, возникает ошибка C2825. Я предполагаю, что это означает, что более общие методы PIMPL тоже не помогут? – Gili

+0

Если вы измените определение класса Array, как указано в сообщении j_random_hacker, оно должно работать, то есть ошибка C2825 должна исчезнуть. – Haspemulator

1

EDIT: ОП отредактировал вопрос, чтобы устранить проблему с размером структуры данных, отмеченную мной и rlbond. С этим изменением теперь можно использовать Array<Child> вместо Array<Child*>, так как janm's answer показывает.

Изменение Array<Child> в Array<Child*>, и изменить Array тип, чтобы понять, что он содержит указатели на объекты, а не самих объектов:

Новый Array.h

// E.g. strip_pointer_from<Foo*>::type is Foo 
template <typename T> 
struct strip_pointer_from<T> {}; 

template <typename T> 
struct strip_pointer_from<T*> { 
    typedef T type; 
}; 

template <typename ElementType> 
class Array 
{ 
public: 
    static std::string getType() 
    { 
     return typename strip_pointer_from<ElementType>::type::getType(); 
    } 
}; 

Я бы сильно рекомендуем переосмыслить Array, хотя - есть ли способ использовать простой vector<Child> и просто спросить каждый элемент для его типа?

+1

Ваше добавление неверно - для кода, как указано, T не хранится в Array вообще, поэтому нет рекурсивного хранилища. Если созданный массив функционирует аналогично std :: vector , то родительский элемент содержит ноль или более дочерних объектов, изначально нулевых, а хранилище для дальнейших детей распределяется динамически по мере необходимости. struct Node {std :: vector дети; } является совершенно законным, если он скорее подвержен эффектам перераспределения. –

+0

@Pete: Вы правы - OP изменил сообщение об ошибке, о котором я упоминал в этом добавлении после того, как написал это. Теперь, когда он имеет конечный размер, 'Array ' отлично работает, как показывает ответ janm. –

0

Ниже приведено, как я пытался решить проблему в системах, требующих метаклассовой информации. См. Также другой ответ для более прямого решения вашей проблемы.


Образец, используемый в stl, должен использовать тип для представления типа, а не строки. Таким образом, std::vector<T>::value_type представляет тип, хранящийся в векторе. Именно тогда клиентский код использует этот тип.

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

Object.h

#ifndef OBJECT_H 
#define OBJECT_H 

#include <string> 

template <typename T> 
struct type_info { 
    // extend type_info<void*> to inherit defaults 
    const static bool is_array = false; 
}; 

template <typename T> 
std::string type_name (const T&) 
{ 
    return type_info<T>::name(); 
}; 

template <typename T> 
std::string type_name() 
{ 
    return type_info<T>::name(); 
}; 

#endif 

Родитель.ч

#include "Object.h" 
#include "Array.h" 

class Child; 
class Parent 
{ 
public: 
    Array<Child> getChildren() { 
     return Array<Child>(); 
    } 
}; 

template <> 
struct type_info <Parent> : public type_info<void*> { 
    static std::string name() { 
     return "parent"; 
    } 
}; 


#endif 

Array.h

template <typename ElementType> 
class Array 
{ 
public: 
    typedef ElementType value_type; 
}; 

template <typename T> 
struct type_info <Array<T > > { 
    static std::string name() { 
     return "Array<" + type_name<T>() + ">"; 
    } 

    const static bool is_array = true; 
}; 

Child.h

#ifndef CHILD_H 
#define CHILD_H 

#include "Parent.h" 

class Child: public Parent 
{ 
}; 

template <> 
struct type_info <Child> : public type_info<void*> { 
    static std::string name() { 
     return "child"; 
    } 
}; 

#endif 

main.cpp

#include "Object.h" 
#include "Parent.h" 
#include "Child.h" 

#include <iostream> 
#include <iomanip> 

using std::cout; 
using std::endl; 
using std::boolalpha; 

template<typename T> bool type_is_array (const T&) { return type_info<T>::is_array; } 

int main(int argc, char* argv[]) 
{ 
    Parent parent; 
    cout << type_name<Parent>() << endl; 
    cout << type_name(parent.getChildren()) << endl; 
    cout << boolalpha << type_is_array(parent) << endl; 
    cout << boolalpha << type_is_array(parent.getChildren()) << endl; 
    return 0; 
} 
Смежные вопросы