在现有的C侵入式链表上强加类型安全性

时间:2013-11-21 04:45:01

标签: c++

我有一组相当简单的双向链接列表助手,围绕两个基本结构:

typedef struct DoubleLinkedListNode_struct {
   struct DoubleLinkedListNode_struct *m_prev;
   struct DoubleLinkedListNode_struct *m_next;
} DoubleLinkedListNode;

typedef struct DoubleLinkedList_struct {
   DoubleLinkedListNode m_anchor;
} DoubleLinkedList;

这些原始部分用于在整个结构和(现在)类中构造侵入式链表。我试图用一整套注意事项和要求来重新设计这组结构和支持功能。如果你想要背景阅读'------'之后的部分。

我的第一次尝试使用旧的Node结构并提供了一个新的类型安全的List模板:

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

只有在编译时无法解析instance-&gt; * 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,memberPointer)并返回一个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 ++,因此只有在TypeName未通过虚拟继承访问memberName时,GetObjectFromMember才有效。如果GetObjectFromMember不安全,我需要在编译时失败的任何解决方案。

所以,目标是:

o-&gt; 允许继续使用现有的DoubleLinkedListNode类型名称
o-&gt; 如果可能,允许继续使用现有的DoubleLinkedList类型名称(我怀疑这是不可能的)
o-&gt; 允许继续使用现有的宏(DoubleLinkedListNode_pushBack和(很多)其他)。
o-&gt; 编译虚拟继承的时间错误,这会破坏GetObjectFromMember的使用
o-&gt; 用例:

DoubleLinkedList_pushBack( &client->m_serviceProvider.m_clientList,
                           &client->m_serviceNode );

- &gt; 可以自由替换:

   client->m_serviceProvider.m_clientList.pushBack(client);

o-&gt; 重新处理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-&gt; 没有任何动态分配。
o-&gt; 与现有实施相比,(显存)或更慢(cpu)不显着(数量级)。

1 个答案:

答案 0 :(得分:1)

事实1:表达式&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将被推断为声明成员的类。