2011-01-19 2 views
0

Скажем, у меня есть следующие:Самый эффективный способ наследования структур на C++?

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

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

+0

Не используется 'operator <<()' используется в основном для работы с * текстом * потоков, а не двоичных потоков? – Karmastan

+0

Просто перегрузите оператора. –

+0

@ Karmastan: Я почти никогда не представляю. Это, однако, полезно для написания в них. –

ответ

1

Общий шаблон для решения бинарных сетевых протоколов определения структуры, которая содержит союз:

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

Что с нормальным наследованием C++?

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

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

+0

+1. Миф о том, что вам нужно сделать вещи полиморфными для использования наследования в C++, кажется, становится все более распространенным; Бог знает почему. –

+2

Это, очевидно, работает, но я бы сказал, что это не так важно, как композиция, семантически. «Msg1» не является «HDR». –

+1

Правда, но когда мне приходится выбирать между простотой и семантическим фанатиком, я стараюсь выбирать простоту. Это мое мнение, хотя оба варианта работают. –

10

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

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

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

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

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

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

+0

+1 это очень хороший совет –

2

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

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, либо явно, добавив свою собственную информацию о типе. На самом деле это не очень хороший способ обойти это.

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

+0

Почему, по-видимому, ненужная дружба? –

+0

@Noah Roberts- Хорошая точка; они могут просто быть свободными, другими словами. Я так привык писать их как друзей, что я все равно его поддерживал. :-) – templatetypedef

+0

Вы должны разбить эту привычку как можно скорее, IMNSHO. См. Myers: http://www.drdobbs.com/184401197 –

Смежные вопросы