开发者

Templates, Circular Dependencies, Methods, oh my!

开发者 https://www.devze.com 2023-01-05 22:11 出处:网络
Background: I am working on a framework that generates C++ code based on an existing Java class model. For this reason I cannot change the circular dependency mentioned below.

Background: I am working on a framework that generates C++ code based on an existing Java class model. For this reason I cannot change the circular dependency mentioned below.

Given:

  • A Parent-Child class relationship
  • Parent contains a list of Children
  • Users must be able to look up the list element type at run-time

I've modeled this in the f开发者_运维百科ollowing testcase:

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. When I compile the above code I get: error C2027: use of undefined type 'Child' at return ElementType::getType();

  2. If I try #include "Child.h" instead of the forward declaration I get: error C2504: 'Parent' : base class undefined at class Child: public Parent

  3. If I try Array<Child*> instead of Array<Child> I get: error C2825: 'ElementType': must be a class or namespace when followed by '::' at return ElementType::getType();

The circular dependency comes about because:

  1. Child.h needs to know about class Parent
  2. Parent.h needs to know about class Array
  3. Array.h needs to know about class Child

Any ideas?


The error is due to the Child class not being present when the template is instantiated.

Add the following either to Main or at the end of Parent.h:

#include "Child.h"

This compiles fine with both g++ 4 and VS 2010.


One way to resolve this problem is to separate the implementations from the interfaces.

So, put the implementation of Parent into a .cpp file, so that the compiler can see the definitions of Parent, Child and Array when compiling 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

And in parent.cpp:

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

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

// etc.

Update:

Yes, the actual problem is caused by Array::getType() being instantiated without a definition of Child being present, so my solution is incomplete.

Pete Kirkham's solution is good: just include child.hpp into main.

For a interface/implementation separation to work, a separate implementation file for Array would be required with explicit instantiation of Array and any other required instantiations. This is probably not what you want, but for completeness, it would look something like:

In array.hpp:

#ifndef ARRAY_HPP
#define ARRAY_HPP

#include <string>

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

#endif

And in array.cpp:

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

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

template class Array<Child>;


EDIT: The OP has edited the question to remove the infinite-size-data-structure problem noticed by myself and rlbond. With this change, it's now possible to use Array<Child> instead of Array<Child*>, as janm's answer shows.

Change Array<Child> to Array<Child*>, and alter the Array type to understand that it contains pointers to objects instead of the objects themselves:

New 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();
    }
};

I would strongly recommend rethinking Array, though -- is there any way you can use a plain vector<Child> and just ask each element for its type?


Maybe you could use pointer to Child in Parent instead? Something like

#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

Or, more generally, maybe it is possible to use compiler firewall technique (a.k.a. opaque pointer, a.k.a. PIMLP). More info about it here.


The following is how I've tended to solve the problem in systems requiring metaclass information. See also other answer for a more direct solution to your problem.


The pattern used in the stl is to use a type to represent a type, not a string. So std::vector<T>::value_type represents the type stored in the vector. It is then up to the client code to make use of this type.

If you want the runtime type of the object, use a base class which has a virtual function that returns the type. For the static type of a place, you can use partial specialisation:

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

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号