Note that there are some explanatory texts on larger screens.

plurals
  1. POImposing type safety on existing C intrusive linked lists
    primarykey
    data
    text
    <p>I have a fairly simple set of doubly linked list helpers centered around two basic structs:</p> <pre><code>typedef struct DoubleLinkedListNode_struct { struct DoubleLinkedListNode_struct *m_prev; struct DoubleLinkedListNode_struct *m_next; } DoubleLinkedListNode; typedef struct DoubleLinkedList_struct { DoubleLinkedListNode m_anchor; } DoubleLinkedList; </code></pre> <p>These primitive pieces are used to construct intrusive linked lists in a whole host of structures and (now) classes. I'm attempting to rework this set of structures and supporting functions with a whole ream of caveats and requirements. If you want the background read the part below the '------' break.</p> <p>My first attempt uses the old Node structures and provides a new type-safe List template:</p> <pre><code>template&lt;typename Type, DoubleLinkedListNode Type::*MP&gt; struct NewDoubleLinkedList { DoubleLinkedListNode m_anchor; //static const int Offset = ((unsigned int)(&amp;(((Type *)0)-&gt;*MP))); ///&lt;- broken void pushBack(Type *inst) { DoubleLinkedList_pushBack(this,&amp;(inst-&gt;*MP)); } Type *begin() { return GetObjectFromMember(Type,*MP,m_anchor.m_next); } Type *end() { return GetObjectFromMember(Type,*MP,&amp;m_anchor); } Type *getNext(Type *from) { return GetObjectFromMember(Type,*MP,from-&gt;*MP.m_next); } }; </code></pre> <p>And this template is used like this:</p> <pre><code>struct MyClient { DoubleLinkedListNode m_serviceNode; }; void testNewList() { NewDoubleLinkedList&lt;MyClient,&amp;MyClient::m_serviceNode&gt; newList; MyClient testClient; newList.pushBack(&amp;testClient); MyClient *client = newList.begin(); while(client != newList.end()) { ASSERT(client == &amp;testClient); client = newList.getNext(client); } DoubleLinkedListNode_remove(&amp;testClient.m_serviceNode); } </code></pre> <p>This all seems to work, and complains correctly, except this code:</p> <pre><code> static const int Offset = ((unsigned int)(&amp;(((Type *)0)-&gt;*MP))); </code></pre> <p>which is intended to fail (at compile time) only if instance->*MP can't be resolved at compile time (i.e. it relies on the instance's virtual table, due to virtual inheritance).</p> <p><strong>Is there some way I can fix this code, or some alternate way to prevent potential confusion with virtual inheritance?</strong></p> <p>On the off chance you think I'm on the complete wrong path I've included the (overly long) background on what I'm doing and my requirements below). Otherwise just <strong>stop here</strong>.</p> <hr> <p>First, I'm going to stress that this is existing code written in C. It's used all over the place so I need a method which allows slow roll-out without having to rewrite every piece of code that uses the list structures at once.</p> <p>The typical use case usually goes something like this:</p> <pre><code>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( &amp;client-&gt;m_serviceProvider.m_clientList, &amp;client-&gt;m_serviceNode ); } void Service_handleClients( MyService *service ) { DoubleLinkedListNode *iter = DoubleLinkedList_begin(&amp;service-&gt;m_clientList); DoubleLinkedListNode *end = DoubleLinkedList_end(&amp;service-&gt;m_clientList); while(iter != end) { MyClient *client = GetObjectFromMember( MyClient, m_serviceNode, iter ); iter = DoubleLinkedListNode_getNext(iter); client-&gt;v_doSomethingLater(client); } } </code></pre> <p>The (uber evil and absolutely everywhere) macro GetObjectFromMember takes (TypeName,memberName,memberPointer) and returns a Typed pointer such that:</p> <pre><code> TypeName *result = GetObjectFromMember(TypeName,memberName,memberPointer); ASSERT(&amp;result-&gt;memberName == memberPointer); </code></pre> <p>For the truly masochistic it looks like this:</p> <pre><code> #define GetObjectFromMember(ObjectType,MemberName,MemberPointer) \ ((ObjectType *)(((char *)MemberPointer) - ((char *)(&amp;(((ObjectType *)0)-&gt;MemberName))))) </code></pre> <p>My goal is to find the least intrusive way to write some templates that can add type safety to the spots in this code most prone to error:</p> <pre><code> DoubleLinkedList_pushBack( &amp;client-&gt;m_serviceProvider.m_clientList, &amp;client-&gt;m_serviceNode ); </code></pre> <p>Where someone might accidentally use the wrong node, like this:</p> <pre><code> DoubleLinkedList_pushBack( &amp;client-&gt;m_serviceProvider.m_clientList, &amp;client-&gt;m_wrongServiceNode ); </code></pre> <p>which compiles cleanly and leads to a catastrophe during the callback phase where we do:</p> <pre><code> MyClient *client = GetObjectFromMember( MyClient, m_serviceNode, iter ); client-&gt;v_doSomethingLater(client); </code></pre> <p>Since the pointer derived by GetObjectFromMember will be wrong.</p> <p>The other major problem is that since we are now doing C++, GetObjectFromMember works only if TypeName does not reach memberName via virtual inheritance. I need whatever solution I come up with to fail at compile time if GetObjectFromMember isn't safe.</p> <p>So, the goals are:</p> <p><strong>o-></strong> Allow continued use of the existing DoubleLinkedListNode typename<br/> <strong>o-></strong> Allow continued use of the existing DoubleLinkedList typename if possible (I suspect this isn't possible)<br/> <strong>o-></strong> Allow continued use of the existing macros (DoubleLinkedListNode_pushBack and (many) others).<br/> <strong>o-></strong> Compile time errors for virtual inheritance which breaks the GetObjectFromMember usage<br/> <strong>o-></strong> Use case where this:<br/></p> <pre><code>DoubleLinkedList_pushBack( &amp;client-&gt;m_serviceProvider.m_clientList, &amp;client-&gt;m_serviceNode ); </code></pre> <p>&nbsp;&nbsp;<strong>-></strong> can be freely replaced with this:</p> <pre><code> client-&gt;m_serviceProvider.m_clientList.pushBack(client); </code></pre> <p><strong>o-></strong> Use case where the Service_handleClients call is reworked to look something like:</p> <pre><code>void Service_handleClients( MyFabulousService *service ) { MyClient *client = service-&gt;m_clientList.begin(); while(client != service-&gt;m_clientList.end()) { MyClient *nextClient = service-&gt;m_clientList.getNext(client); client-&gt;v_doSomethingLater(client); client = nextClient; } } </code></pre> <p><strong>o-></strong> No dynamic allocation of any kind.<br/> <strong>o-></strong> Not significantly (order of magnitude) larger (memory) or slower (cpu) than existing implementation.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload