使用C ++创建泛型类型 - 具有共享实现的模板

时间:2011-09-20 16:06:16

标签: c++ templates types parametric-polymorphism

例如,考虑一个简单的数据结构,如链表。在C中,它可能看起来像:

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

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

我希望拥有相同的结构和函数,但通过声明data字段的类型来更好地进行类型检查,这将永远是指向某个东西的指针。使用示例:

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

但我不想为每种类型的指针生成一个实现,就像普通模板一样。换句话说,我想要更像Java,ML和其他语言的泛型或参数类型。我只是尝试下面的代码作为测试。无类型的C-like部分最终会进入实现文件,而模板和函数声明将出现在头文件中。我假设它们会被优化掉,我会留下与C版本大致相同的机器代码,除非它会被类型检查。

但是我对C ++并不擅长......有没有办法改进这个,或者使用更多惯用的C ++,也许是模板专业化?

#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);

}

3 个答案:

答案 0 :(得分:4)

这正是Boost.PointerContainer所做的,检查其实现。基本上它的作用是实现void*的专门化,并向其中任何其他实现转发static_cast参数。

答案 1 :(得分:3)

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

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

这对于C来说很常见,但对于C ++则不常见。在C ++中,它通常如下所示:

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

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

请注意重要的区别 - 为了共享实现,C版本具有另一级别的间接,而C ++版本不需要这样做。这意味着C版本具有另外n个动态内存分配,其中n是列表中的项目数。鉴于每个分配通常需要获得全局锁定,每个分配通常至少有16个字节的开销,以及内存管理器为该方带来的所有开销,C ++版本的优势并非无关紧要,尤其是当您包含时考虑事项中的缓存局部性等。

换句话说,对于Node<int>,C ++版本存储int,而C版本存储int *,以及int的动态分配。

这当然折扣了链表在90%的情况下是一个可怕的数据结构。

如果必须使用链表,并且必须对数据成员使用动态分配,那么“用void* s替换指针”的想法并非不合理。但是,如果您可以访问C ++ 11编译器(VS2010,最近的GCC版本等),那么您应该使用T声明依赖于std::is_pointer作为指针类型的断言和static_assert,你应该在接口方法中使用static_cast而不是C风格的强制转换。 C样式演员会让某人做Node<SomeTypeBiggerThanVoidPtr>,它会编译,但会在运行时爆炸。

答案 2 :(得分:1)

正如其他答案和评论所说,使用std :: forward_list或其他现有库。如果你拒绝,这就像我会做的那样:

#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 请记住,此对象不会真正封装下一个或数据,因此您必须自己管理所有这些。