使用 C++创建通用类型 - 具有共享实现的模板
作为一个例子,考虑一个简单的数据结构,比如链表。在 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 的部分最终将进入实现文件,而模板和函数声明将位于头文件中。我假设它们会被优化掉,我会留下与 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);
}
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);
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这正是
Boost.PointerContainer
所做的,检查它的实现。基本上,它的作用是实现void*
的专门化,并将任何其他实现转发给它static_cast
传入和传出参数。This is exactly what
Boost.PointerContainer
does, check its implementation. Basically what it does is implement the specialization forvoid*
, and have any other implementation forward to itstatic_cast
ing the parameters in and out.这对于 C 来说很常见,但对于 C++ 来说却不常见。在 C++ 中,它通常如下所示:
请注意重要的区别 - C 版本具有另一级间接性以便共享实现,而 C++ 版本不需要这样做。这意味着 C 版本还有另一个
n
动态内存分配,其中n
是列表中的项目数。鉴于每次分配通常都需要获取全局锁,每次分配通常至少有 16 字节的开销,以及内存管理器给各方带来的所有开销,C++ 版本的优势并非微不足道,特别是当您包括诸如缓存局部性之类的考虑因素。换句话说,对于
Node
,C++ 版本存储一个int
,而 C 版本存储一个int *
以及一个int
的动态分配。当然,这并没有考虑到链表在 90% 的情况下都是一种可怕的数据结构。
如果你必须使用链表,并且如果你必须使用数据成员的动态分配,那么你的“用
void*
s替换指针”的想法也不无道理。但是,如果您可以访问 C++11 编译器(VS2010、最新的 GCC 版本等),则应该使用T
来放置一个断言,表明您依赖于T
作为指针类型。 >std::is_pointer 和static_assert
,并且您应该在接口方法中使用static_cast
而不是 C 风格的强制转换。 C 风格的强制转换可以让某人执行Node
,它会编译,但在运行时会爆炸。This is common for C, but not for C++. In C++ it usually looks like this:
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, wheren
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 anint
, while the C version stores anint *
, along with a dynamic allocation for theint
.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 onT
being a pointer type, usingstd::is_pointer
andstatic_assert
, and you should usestatic_cast
rather than C-style casts in your interface methods. The C style cast would let someone doNode<SomeTypeBiggerThanVoidPtr>
, and it would compile, but explode at runtime.正如其他答案和评论所说,使用 std::forward_list 或另一个现有库。如果你拒绝,这更像是我会做的:
http://ideone.com/xseYk
请记住,该对象并不真正封装 next 或数据,因此您必须自己管理所有这些。
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:
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.