Обеспечение безопасности типов в существующих навязчивых связанных списках C

0

У меня довольно простой набор двунаправленных списков, в которых сосредоточены две основные структуры:

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

typedef struct DoubleLinkedList_struct {
   DoubleLinkedListNode m_anchor;
} DoubleLinkedList;

Эти примитивные фрагменты используются для построения интрузивных связанных списков во множестве структур и (сейчас) классов. Я пытаюсь переделать этот набор структур и поддерживающих функций с целым рядом оговорок и требований. Если вы хотите, чтобы фон читал часть ниже '------' break.

Моя первая попытка использует старые структуры узлов и предоставляет новый тип списка:

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

Макрос (uber evil и абсолютно повсеместно) GetObjectFromMember принимает (TypeName, memberName, memberPointer) и возвращает типизированный указатель таким образом, что:

   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 не достигает имени элемента через виртуальное наследование. Мне нужно какое-то решение, с которым я столкнулся, сбой во время компиляции, если GetObjectFromMember небезопасен.

Итак, цели:

o-> Разрешить продолжение использования существующего имени DoubleLinkedListNode
o-> Разрешить дальнейшее использование существующего имени DoubleLinkedList, если это возможно (я подозреваю, что это невозможно)
o-> Разрешить продолжение использования существующих макросов (DoubleLinkedListNode_pushBack и (многие) другие).
o-> Ошибки времени компиляции для виртуального наследования, которые ломают использование GetObjectFromMember
o-> Используйте случай, когда это:

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

-> можно свободно заменить на это:

   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-> Не значительно (по порядку величины) больше (память) или медленнее (процессор), чем существующая реализация.

Теги:

1 ответ

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 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 будет выведен как класс, в котором объявлен член.

  • 0
    Это похоже на то, что мне нужно, за исключением того, что большинство моих компиляторов еще не поддерживают decltype. Тем не менее, это похоже на правильное решение для тех компиляторов, которые делают. Позвольте мне исследовать альтернативы, подобные этому, и я перезвоню вам.
  • 0
    Это было определенно выполнимо. Я закончил тем, что делал вывод типа шаблона вместо decltype: template <typename RequiredType, typename MemberType, typename BaseType> встроенное имя типа typen RequiredType * memberStaticCastTo (MemberType BaseType :: * memberPointer, MemberType * memberInstance)
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню