Шаблоны, круговые зависимости, методы, о боже! - PullRequest
2 голосов
/ 05 июля 2010

Фон : я работаю над 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 должен знать о классе Array
  3. Array.h нужно знать о классе Child

Есть идеи?

Ответы [ 5 ]

4 голосов
/ 05 июля 2010

Ошибка связана с тем, что дочерний класс отсутствует при создании экземпляра шаблона.

Добавьте следующее либо в Main, либо в end Parent.h:

#include "Child.h"

Это прекрасно работает как с g ++ 4, так и с VS 2010.

4 голосов
/ 05 июля 2010

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

Итак, поместите реализацию Parent в файл .cpp, чтобы компилятор мог видеть определения Parent, Child и Array при компиляции 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

И в parent.cpp:

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

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

// etc.

Обновление:

Да, настоящая проблема вызвана тем, что Array :: getType () создается без определения Child, поэтому моё решение неполное.

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

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

В файле 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>;
1 голос
/ 05 июля 2010

РЕДАКТИРОВАТЬ: ОП отредактировал вопрос, чтобы устранить проблему структуры данных бесконечного размера, замеченную мной и rlbond.С этим изменением теперь можно использовать Array<Child> вместо Array<Child*>, как показывает janm .

Изменить 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> и просто спросить каждый элемент для его типа?

0 голосов
/ 05 июля 2010

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


Шаблон, используемый в 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

Parent.h

#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;
}
0 голосов
/ 05 июля 2010

Может быть, вы могли бы вместо этого использовать указатель на 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). Подробнее об этом здесь .

...