使用C++生成泛型类型-一个具有共享实现的模板
Using C++ to make a generic type - a template with shared implementation
例如,考虑一个简单的数据结构,如链表。在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);
}
这正是Boost.PointerContainer
所做的,请检查其实现。基本上,它所做的是实现void*
的专用化,并将参数输入和输出的任何其他实现转发给static_cast
。
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版本等(,则应该使用std::is_pointer
和static_assert
来断言您依赖于T
作为指针类型,并且应该在接口方法中使用static_cast
而不是C样式转换。C风格的强制转换会让人执行Node<SomeTypeBiggerThanVoidPtr>
,它会编译,但会在运行时爆炸。
正如其他答案和注释所说,使用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请记住,这个对象并没有真正封装next或数据,所以您必须自己管理所有这些。
- 如何创建一个共享对象与另一个.所以在Cmake
- 如何将所有权从一个共享指针向量转移到另一个向量?
- 构建并使用使用另一个共享库的源文件中的共享库.(Rinside)
- Cmake构建一个共享库,其中包含其所有依赖关系
- 如何根据目标体系结构将共享库链接到另一个共享库
- "make"的输出是一个共享对象,而不是可执行文件
- 如何知道一个共享库是否取决于另一个共享库
- 如何将共享库嵌入到另一个共享库中
- 共享内存空间可以将数据(非POD)发送到另一个共享内存吗?
- 创建一个共享的内存矢量
- 在一个共享对象中提升 python 多个模块
- 链接到链接到另一个共享库的共享库会在退出时出错
- 将共享库链接到另一个共享库
- 将共享库与 Linux 中的另一个共享库链接
- 从C++中传递一个共享指针/在堆上放置一个共享指示器
- Opengl和opencl在一个共享上下文中只能使用一个内核
- 在linux中使用另一个共享库构建共享库
- 如何从Ocaml调用C++代码,使其本身成为一个共享库
- 实现一个共享库的纯虚函数,并在库中调用它
- 如何使用CMake命令/设计一个共享库,使用更高的包括