将类型安全强加给现有的C侵入式链表
Imposing type safety on existing C intrusive linked lists
我有一组相当简单的双链表帮助程序,它们以两个基本结构为中心:
typedef struct DoubleLinkedListNode_struct {
struct DoubleLinkedListNode_struct *m_prev;
struct DoubleLinkedListNode_struct *m_next;
} DoubleLinkedListNode;
typedef struct DoubleLinkedList_struct {
DoubleLinkedListNode m_anchor;
} DoubleLinkedList;
这些基元片段用于在一大堆结构和(现在)类中构建侵入式链表。我正试图通过一系列的注意事项和需求来重新设计这组结构和支持功能。如果你想要背景,请阅读"------"分隔符下面的部分。
我的第一次尝试使用了旧的Node结构,并提供了一个新的类型安全的列表模板:
template<typename Type, DoubleLinkedListNode Type::*MP>
struct NewDoubleLinkedList
{
DoubleLinkedListNode m_anchor;
//static const int Offset = ((unsigned int)(&(((Type *)0)->*MP))); ///<- broken
void pushBack(Type *inst) { DoubleLinkedList_pushBack(this,&(inst->*MP)); }
Type *begin() { return GetObjectFromMember(Type,*MP,m_anchor.m_next); }
Type *end() { return GetObjectFromMember(Type,*MP,&m_anchor); }
Type *getNext(Type *from) { return GetObjectFromMember(Type,*MP,from->*MP.m_next); }
};
这个模板是这样使用的:
struct MyClient {
DoubleLinkedListNode m_serviceNode;
};
void testNewList()
{
NewDoubleLinkedList<MyClient,&MyClient::m_serviceNode> newList;
MyClient testClient;
newList.pushBack(&testClient);
MyClient *client = newList.begin();
while(client != newList.end()) {
ASSERT(client == &testClient);
client = newList.getNext(client);
}
DoubleLinkedListNode_remove(&testClient.m_serviceNode);
}
这一切似乎都起作用了,并且正确地抱怨,除了这个代码:
static const int Offset = ((unsigned int)(&(((Type *)0)->*MP)));
只有当实例->*MP无法在编译时解析(即,由于虚拟继承,它依赖于实例的虚拟表)时,它才会失败(在编译时)。
是否有某种方法可以修复此代码,或者有其他方法可以防止与虚拟继承混淆
如果你认为我走错了路,我已经在下面列出了我正在做的事情的(过长的)背景和我的要求)。否则,请在此停止。
首先,我要强调的是,这是用C编写的现有代码。它到处都在使用,所以我需要一种方法,它可以缓慢地推出,而不必一次重写使用列表结构的每一段代码。
典型的用例通常是这样的:
struct MyService {
... //other backend service data
DoubleLinkedList m_clientList;
}
struct MyClient {
... //other client service data
MyService *m_serviceProvider;
DoubleLinkedListNode m_serviceNode;
DoubleLinkedListNode m_wrongServiceNode;
void (*v_doSomethingLater)( MyClient *); //"virtual" function
}
void Client_requestService( MyClient *client )
{
... //prep work for service request
DoubleLinkedList_pushBack( &client->m_serviceProvider.m_clientList,
&client->m_serviceNode );
}
void Service_handleClients( MyService *service )
{
DoubleLinkedListNode *iter = DoubleLinkedList_begin(&service->m_clientList);
DoubleLinkedListNode *end = DoubleLinkedList_end(&service->m_clientList);
while(iter != end) {
MyClient *client = GetObjectFromMember( MyClient, m_serviceNode, iter );
iter = DoubleLinkedListNode_getNext(iter);
client->v_doSomethingLater(client);
}
}
(超级邪恶且无处不在)宏GetObjectFromMember获取(TypeName、memberName、member pointer)并返回Typed指针,使得:
TypeName *result = GetObjectFromMember(TypeName,memberName,memberPointer);
ASSERT(&result->memberName == memberPointer);
对于真正的受虐狂来说,它看起来是这样的:
#define GetObjectFromMember(ObjectType,MemberName,MemberPointer)
((ObjectType *)(((char *)MemberPointer) - ((char *)(&(((ObjectType *)0)->MemberName)))))
我的目标是找到一种侵入性最小的方法来编写一些模板,这些模板可以为代码中最容易出错的地方添加类型安全性:
DoubleLinkedList_pushBack( &client->m_serviceProvider.m_clientList,
&client->m_serviceNode );
有人可能不小心使用了错误的节点,比如:
DoubleLinkedList_pushBack( &client->m_serviceProvider.m_clientList,
&client->m_wrongServiceNode );
它干净地编译,并在我们所做的回调阶段导致灾难:
MyClient *client = GetObjectFromMember( MyClient, m_serviceNode, iter );
client->v_doSomethingLater(client);
由于GetObjectFromMember派生的指针将是错误的。
另一个主要问题是,由于我们现在使用C++,GetObjectFromMember只有在TypeName未通过虚拟继承到达memberName时才有效。如果GetObjectFromMember不安全,我需要我想出的任何解决方案在编译时失败。
因此,目标是:
o->允许继续使用现有的DoubleLinkedListNode类型名
o->如果可能的话,允许继续使用现有的DoubleLinkedList类型名称(我怀疑这是不可能的)
o->允许继续使用现有宏(DoubleLinkedListNode_pushBack和(许多)其他宏)
o->虚拟继承的编译时错误,破坏了GetObjectFromMember的使用
o->用例,其中:
DoubleLinkedList_pushBack( &client->m_serviceProvider.m_clientList,
&client->m_serviceNode );
nbsp->可以自由替换为:
client->m_serviceProvider.m_clientList.pushBack(client);
o->Service_handleClients调用被重写为类似的用例
void Service_handleClients( MyFabulousService *service )
{
MyClient *client = service->m_clientList.begin();
while(client != service->m_clientList.end()) {
MyClient *nextClient = service->m_clientList.getNext(client);
client->v_doSomethingLater(client);
client = nextClient;
}
}
o->没有任何类型的动态分配
o->与现有实现相比,没有显著(数量级)大(内存)或慢(cpu)。
&Type::member
的类型为MemberType ClassType::*
,其中ClassType
是声明member
的类,而不一定是Type
。
事实2:只有当基类不是虚拟基(也不是模糊基)时,static_cast
从基类到派生类的引用或指针才是合法的(尽管并不总是安全的)。在我看来,这正是你每次使用GetObjectFromMember
时想要检查的东西。
那么:怎么样
// Precondition: mptr points at the specified member of a ClassType object.
// Precondition: member must not be in a virtual base class of ClassType.
//
// The second precondition is not an issue if the template arguments are
// deduced from an &AnyClass::member expression, since ClassType will
// deduce as the member's actual enclosing class.
template<typename ClassType, typename MemberType>
ClassType* ObjectContainingMember(MemberType ClassType::*member,
MemberType *mptr)
{
ClassType* dummy = 0;
std::size_t member_offset =
reinterpret_cast<char*>(&(dummy->*member)) -
reinterpret_cast<char*>(dummy);
char* obj_addr =
reinterpret_cast<char*>(mptr) - member_offset;
return reinterpret_cast<ClassType*>(obj_addr);
}
// Precondition: MemberPointer points at the specified member of
// an ObjectType object. Returns a pointer to that ObjectType.
#define GetObjectFromMember(ObjectType,MemberName,MemberPointer)
static_cast<ObjectType*>(ObjectContainingMember(
&ObjectType::MemberName, MemberPointer))
由于事实1,模板参数ClassType
将被推导为声明成员的类。
- 为什么将函数的返回类型从结构节点*更改为void后,链表的元素没有显示create_ll和显示?
- 基于给定字符串数据类型的链表删除节点
- 结构内具有更多数据类型的单个链表
- 尝试连接两种不同类型的结构来创建链表
- 此代码安全吗?(链表,C++)
- 'Node'不命名类型链表
- 数据类型为结构的链表
- 为什么链表不是线程安全的?
- 在单个链表中擦除和插入线程安全吗
- 为什么在链表中使用指向结构的指针而不是常规结构类型变量呢
- 将类型安全强加给现有的C侵入式链表
- 编译器错误,链表:错误:"->"的基本操作数具有非指针类型"IntNodeType"
- 数据类型匹配,但我的链表仍然出错
- 返回链表的正确迭代器类型
- 简单抽象数据类型链表
- 带嵌套类的链表:如何在声明嵌套类类型之前使用它
- 构造函数不允许返回类型(泛型链表)
- "*"标记之前的预期构造函数、析构函数或类型转换 模板化链表
- 链表输入类型问题
- 由模板类型转换的链表错误