例如,考虑一个简单的数据结构,如链表。在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);
}
答案 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 请记住,此对象不会真正封装下一个或数据,因此您必须自己管理所有这些。