views:

188

answers:

1

I have defined a generic tree-node class like this:

template<class DataType>
class GenericNode
{
public:
    GenericNode() {}

    GenericNode(const DataType & inData) : mData(inData){}

    const DataType & data() const
    { return mData; }

    void setData(const DataType & inData)
    { mData = inData; }

    size_t getChildCount() const
    { return mChildren.size(); }

    const GenericNode * getChild(size_t idx) const
    { return mChildren[idx]; }

    GenericNode * getChild(size_t idx)
    { return mChildren[idx]; }

    void addChild(GenericNode * inItem)
    { mChildren.push_back(inItem); }

private:
    DataType mData;
    typedef std::vector<GenericNode*> Children;
    Children mChildren;
};
typedef GenericNode<std::string> TreeItemInfo;

And I would like to make it more generic by making the child pointer type customizable. For example to allow using a smart pointer type. Naively I tried this:

template<class DataType, class ChildPtr>
class GenericNode
{
public:
    GenericNode() {}

    GenericNode(const DataType & inData) : mData(inData){}

    const DataType & data() const
    { return mData; }

    void setData(const DataType & inData)
    { mData = inData; }

    size_t getChildCount() const
    { return mChildren.size(); }

    const ChildPtr getChild(size_t idx) const
    { return mChildren[idx]; }

    ChildPtr getChild(size_t idx)
    { return mChildren[idx]; }

    void addChild(ChildPtr inItem)
    { mChildren.push_back(inItem); }

private:
    DataType mData;
    typedef std::vector<ChildPtr> Children;
    Children mChildren;
};

typedef GenericNode<std::string, GenericNode<std::string > * > TreeItemInfo;

However, that doesn't work of course because I need to specify the second parameter for the second parameter for the second parameter etc... into eternity.

Is there a way to solve this puzzle?

EDIT

I found a solution based on @Asaf's answer. For those interested, below is a full code sample (comments are welcome).

EDIT2

I modified the interface so that externally always raw pointers are used.

#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <assert.h>


template <class PointeeType>
struct NormalPointerPolicy
{
    typedef PointeeType* PointerType;

    static PointeeType* getRaw(PointerType p)
    {
        return p;
    }
};

template <class PointeeType>
struct SharedPointerPolicy
{
    typedef boost::shared_ptr<PointeeType> PointerType;

    static PointeeType* getRaw(PointerType p)
    {
        return p.get();
    }
};


template <class DataType, template <class> class PointerPolicy>
class GenericNode
{
public:    
    GenericNode() { }

    GenericNode(const DataType & inData) : mData(inData) { }

    typedef GenericNode<DataType, PointerPolicy> This;

    typedef typename PointerPolicy<This>::PointerType ChildPtr;

    const This * getChild(size_t idx) const
    { return PointerPolicy<This>::getRaw(mChildren[idx]); }

    This * getChild(size_t idx)
    { return PointerPolicy<This>::getRaw(mChildren[idx]); }

    void addChild(This * inItem)
    { 
        ChildPtr item(inItem);
        mChildren.push_back(item);
    }

    const DataType & data() const
    { return mData; }

    void setData(const DataType & inData)
    { mData = inData; }

private:
    DataType mData;
    std::vector<ChildPtr> mChildren;
};

typedef GenericNode<std::string, NormalPointerPolicy> SimpleNode;
typedef GenericNode<std::string, SharedPointerPolicy> SmartNode;


int main()
{
    SimpleNode simpleNode;
    simpleNode.addChild(new SimpleNode("test1"));
    simpleNode.addChild(new SimpleNode("test2"));
    SimpleNode * a = simpleNode.getChild(0);
    assert(a->data() == "test1");
    const SimpleNode * b = static_cast<const SimpleNode>(simpleNode).getChild(1);
    assert(b->data() == "test2");

    SmartNode smartNode;
    smartNode.addChild(new SmartNode("test3"));
    smartNode.addChild(new SmartNode("test4"));
    SmartNode * c = smartNode.getChild(0);
    assert(c->data() == "test3");
    SmartNode * d = static_cast<const SmartNode>(smartNode).getChild(1);
    assert(d->data() == "test4");
    return 0;
}
+2  A: 

Not the way you look at it. You should combine some kind of inheritance here. Try this, for example:

template <class PointeeType>
struct NormalPointerPolicy
{
 typedef PointeeType* PointerType;
};

template <class PointeeType>
struct SmartPointerPolicy
{
 typedef MySmartPtrClass<PointeeType> PointerType;
};

template <class DataType>
class BaseGenericNode
{
public:
 BaseGenericNode() {}

 BaseGenericNode(const DataType & inData) : mData(inData){}

 const DataType & data() const
 { return mData; }

 void setData(const DataType & inData)
 { mData = inData; }

protected:
 DataType mData;

};

template <class DataType, template <class> class PointerPolicy>
class GenericNode : public BaseGenericNode<DataType>
{
 typedef typename PointerPolicy<BaseGenericNode<DataType> >::PointerType ChildPtr;

private:
 typedef std::vector<ChildPtr> Children;
 Children mChildren;
};

The GenericNode is the actual node type, which holds the base type 'BaseGenericNode'.
The base type holds the actual data (and its related functionality), and the derived class holds the links to other nodes.
There are 2 template policy classes for how your pointer actually looks like, and you use them like this:

GenericNode<int, NormalPointerPolicy> instance;
GenericNode<int, SmartPointerPolicy> instance;

The problem (or advantage?) of this implementation is that a node with pointers of one kind, can hold child nodes with pointers of another kind.

Asaf