Question

As an example, consider a simple data structure like a linked list. In C, it might look like:

struct Node
{
    struct Node *next;
    void *data;
};

void *getLastItem(struct Node*);
...

I'd like to have the same struct and functions, but with better type checking by declaring the type of the data field, which will always be a pointer to something. An example use:

Node<Thing*> list = getListOfThings();
Thing *t = list->data;
t = getLastItem(list);
...

But I don't want to generate an implementation for every type of pointer, as happens with a normal template. In other words, I want something more like a generic or parametric type from Java, ML, and other languages. I just tried the code below as a test. The untyped C-like part would eventually go in a implementation file, while the template and function declarations would be in the header file. I'm assuming they would be optimized away and I'd be left with machine code that is about the same as the C version, except it would be type-checked.

But I'm not great with C++... Is there a way to improve this, or use more idiomatic C++, perhaps template specialization?

#include <stdio.h>

struct NodeImpl
{
    NodeImpl *next;
    void *data;
};

void *getLastItemImpl(NodeImpl *list)
{
    printf("getLastItem, non-template implementation.\n");
    return 0;  // not implemented yet
}

template <typename T>
struct Node
{
    Node<T> *next;
    T data;
};

template <typename T>
T getLastItem(Node<T> *list)
{
    return (T)getLastItemImpl((NodeImpl*)list);
}

struct A { };
struct B { };

int main()
{
    Node<A*> *as = new Node<A*>;
    A *a = getLastItem(as);
    Node<B*> *bs = new Node<B*>;
    B *b = getLastItem(bs);

}
Was it helpful?

Solution

As the other answers and comments said, use std::forward_list, or another existing library. If you refuse, this is more like I would do:

#include <stdio.h>

struct NodeImpl
{
    NodeImpl *next;
    void *data;
public:    
    // we have pointers, so fulfill the rule of three
    NodeImpl() : next(NULL), data(NULL) {}
    ~NodeImpl() {}
    NodeImpl& operator=(const NodeImpl& b) {next = b.next; data = b.data; return *this;}
    // This function now a member.  Also, I defined it.
    void* getLastItem()
    {
        if (next)
            return next->getLastItem();
        return data;
    }
    void* getData() {return data;}
    void setData(void* d) {data = d;}
};

// the template _inherits_ from the impl
template <typename T>
struct Node : public NodeImpl
{
    Node<T> operator=(const Node<T>& b) {NodeImpl::operator=(b);}
    // we "redefine" the members, but they're really just wrappers
    T* getLastItem()
    { return static_cast<T*>(NodeImpl::getLastItem());}

    T* getData() {return static_cast<T*>(NodeImpl::getData());}
    void setData(T* d) {NodeImpl::setData(static_cast<void*>(d));}

    //or, if you prefer directness...
    operator T*() {return static_cast<T*>(NodeImpl::getData());}
    Node<T> operator=(T* d) {NodeImpl::setData(static_cast<void*>(d));}  
};


struct A { };
struct B { };

int main()
{
    Node<A> as;  //why were these heap allocated?  The root can be on the stack
    A *a = as.getLastItem();
    Node<B> bs; //also, we want a each node to point to a B, not a B*
    B *b = bs.getLastItem();

    B* newB = new B;
    bs = newB;  //set the data member
    newB = bs;  //read the data member
}

http://ideone.com/xseYk Keep in mind that this object doesn't encapsulate next or data really, so you have to manage all of that yourself.

OTHER TIPS

This is exactly what Boost.PointerContainer does, check its implementation. Basically what it does is implement the specialization for void*, and have any other implementation forward to it static_casting the parameters in and out.

struct Node
{
    struct Node *next;
    void *data;
};

void *getLastItem(struct Node*);
...

This is common for C, but not for C++. In C++ it usually looks like this:

template<typename T>
struct Node
{
    struct Node *next;
    T data;
};

T& getLastItem(const Node&);
...

Note the important difference -- the C version has another level of indirection in order to share implementations, while the C++ version need not do this. This means the C version has another n dynamic memory allocations, where n is the number of items in the list. Given that each allocation usually requires obtaining a global lock, often has at least 16 bytes of overhead per allocation, as well as all the overhead the memory manager brings to the party, the advantage of the C++ version is not insignificant, particularly when you include things like cache locality in the considerations.

Put another way, for Node<int>, the C++ version stores an int, while the C version stores an int *, along with a dynamic allocation for the int.

This of course discounting that a linked list is a horrendous data structure 90% of the time.

If you must use a linked list, and if you must use dynamic allocation for the data members, then your idea of "replace the pointers with void*s" is not unreasonable. However, if you have access to a C++11 compiler (VS2010, recent GCC versions, etc.), you should put in an assert that you depend on T being a pointer type, using std::is_pointer and static_assert, and you should use static_cast rather than C-style casts in your interface methods. The C style cast would let someone do Node<SomeTypeBiggerThanVoidPtr>, and it would compile, but explode at runtime.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top