Самый эффективный способ наследовать структуры в C ++? - PullRequest
0 голосов
/ 20 января 2011

Допустим, у меня есть следующее:

#pragma pack(push,1)

struct HDR {
   unsigned short msgType;
   unsigned short msgLen;
};

struct Msg1 {
   unsigned short msgType;
   unsigned short msgLen;
   char text[20];
};

struct Msg2 {
   unsigned short msgType;
   unsigned short msgLen;
   uint32_t c1;
   uint32_t c2;
};

 .
 .
 .

Я хочу иметь возможность повторно использовать структуру HDR, поэтому мне не нужно продолжать определять два члена: msgType и msgLen.Я не хочу привлекать vtables из соображений производительности, но я хочу переопределить оператор << для каждой из структур.Основываясь на этом последнем требовании, я не понимаю, как я мог бы использовать объединение, поскольку размеры также различаются. </p>

Любые идеи о том, как это можно наилучшим образом обработать для чистой производительности

Ответы [ 4 ]

10 голосов
/ 20 января 2011

Композиция представляется наиболее подходящей здесь:

struct Msg1
{
    HDR hdr;
    char text[20];
};

Хотя вы можете использовать наследование C ++, в данном случае это не имеет смысла семантически; Msg1 не является HDR.

В качестве альтернативы (и, возможно, предпочтительно), вы можете определить абстрактный Msg базовый тип:

struct Msg
{
    HDR hdr;
protected:
    Msg() {}
};

и все ваши конкретные классы сообщений получаются из этого.

8 голосов
/ 20 января 2011

Что не так с обычным наследованием C ++?

struct HDR { ... };
struct Msg1: HDR { ... };

Просто не объявляйте виртуальные функции-члены, и все будет готово.

2 голосов
/ 20 января 2011

В зависимости от того, что вы планируете делать с этими структурами, вы можете сделать это без дополнительных затрат на vtable, например:

struct HDR {
   unsigned short msgType;
   unsigned short msgLen;
};

struct Msg1: HDR {
   char text[20];
   friend ostream& operator<< (ostream& out, const Msg1& msg);
};

struct Msg2: HDR {
   uint32_t c1;
   uint32_t c2;
   friend ostream& operator<< (ostream& out, const Msg2& msg);
};

Поскольку в базовом классе нет виртуальных функций, вы не получите vtable для этих объектов. Однако вы должны знать, что это означает, что если у вас есть указатель HDR, указывающий на произвольный подкласс, вы не сможете его распечатать, поскольку неясно, какую функцию operator<< вызвать.

Я думаю, что здесь может быть более фундаментальная проблема. Если вы пытаетесь обрабатывать все эти объекты единообразно с помощью базового указателя, но хотите иметь возможность распечатать их все, тогда вам понадобится нажать на память, чтобы пометить каждый объект. Вы можете либо пометить их неявно с помощью vtable, либо явно, добавив свою собственную информацию о типе. Там действительно нет хорошего способа обойти это.

Если, с другой стороны, вы просто хотите упростить свою логику, включив элементы данных в базовый класс, тогда этот подход должен работать.

1 голос
/ 20 января 2011

Общий шаблон для работы с двоичными сетевыми протоколами - это определение структуры, содержащей объединение:

struct Message {
   Header hdr;
   union {
      Body1 msg1;
      Body2 msg2;
      Body3 msg3;
   };
};

Семантически вы заявляете, что Message состоит из Header и тела, которое может быть одним из Body1, Body2 ... Теперь, предоставьте операторы вставки и извлечения для заголовка и каждого кузов отдельно. Затем реализуйте те же операторы для Message, вызвав его на Header, и, в зависимости от типа сообщения, тело сообщения, которое имеет смысл.

Обратите внимание, что элементы объединения не обязательно должны иметь одинаковый размер. Размер союза будет максимальным из размеров его членов. Этот подход допускает компактное двоичное представление, которое может быть прочитано / записано из сети. Ваш буфер чтения / записи будет Message, и вы будете читать только заголовок, а затем соответствующее тело.

// Define operators:
std::ostream& operator<<( std::ostream&, Header const & );
std::ostream& operator<<( std::ostream&, Body1 const & ); // and the rest
// Message operator in terms of the others 
std::ostream& opeartor<<( std::ostream& o, Message const & m )
{
   o << m.header;
   switch ( m.header.type ) {
   case TYPE1: o << m.body1; break;
   //...
   };
   return o;
}

// read and dump the contents to stdout
Message message;
read( socket, &message, sizeof message.header );   // swap the endianness, check size...
read( socket &message.msg1, message.header.size ); // ...
std::cout << message << std::endl;
...