Добавить элемент в существующую структуру, не нарушая устаревший код

В некотором устаревшем коде, с которым я работаю, есть следующее определение.

struct VlanData
{
    uint16_t mEtherType;
    uint16_t mVlanId;
};

Я хочу добавить новый элемент в эту структуру:

struct VlanData
{
    uint16_t mEtherType;
    uint16_t mVlanId;
    uint8_t mVlanPriority; // New member
};

Однако использование VlanData было довольно непоследовательным в устаревшем коде.

Не инициализирован при построении:

VlanData myVlans;
myVlans.mEtherType = 0x8100;
myVlans.mVlanId = 100;

Значение инициализировано:

VlanData myVlans = { 0x8100, 100 };

Что я хочу сделать, так это придумать безопасный способ убедиться, что `mVlanPriority' автоматически устанавливается на 0 в устаревшем коде без обновления большого количества кода.

Я понимаю, что могу изменить устаревший код, чтобы инициализировать значения всех членов следующим образом:

VlanData myVlans = {};

Но я не хочу обновлять весь этот код. Я считаю, что создание конструктора по умолчанию, подобного этому, поможет:

VlanData()
: mEtherType(0),
  mVlanId(0),
  mVlanPriority(0)
{}

Но это также разрушило бы POD-ность структуры.

Итак, я думаю, у меня есть несколько вопросов:

  1. Есть ли безопасный способ убедиться, что mVlanPriority установлен в 0 в устаревшем коде без обновления устаревшего кода?
  2. Какое использование класса было бы нарушено, если бы он больше не был типом POD?

person LeopardSkinPillBoxHat    schedule 19.07.2011    source источник
comment
Добавление конструктора сделает недействительными все эти инициализаторы скобок.   -  person Tugrul Ates    schedule 19.07.2011
comment
@junjanes - вы имеете в виду, что код, использующий инициализаторы скобок, больше не будет компилироваться?   -  person LeopardSkinPillBoxHat    schedule 19.07.2011


Ответы (3)


struct VlanData {
    struct P
    {
        uint8_t operator=(uint8_t v) { mVlanPriority = v; }
        uint8_t mVlanPriority; P() { mVlanPriority = 0; };
    };
    uint16_t mEtherType;
    uint16_t mVlanId;
    P mVlanPriority;
 };

Определите другие типы операторов и добавьте функции преобразования типов по мере необходимости.

eg:

int main(int argc, char** argv)
{
    VlanData myVlans0 = { };
    VlanData myVlans = { 0x8100, 100 };
    myVlans.mVlanPriority = 10;
}
person Murali VP    schedule 19.07.2011
comment
Является ли VLanData все еще Pod с этим? Если VlanData используется в объединении или переинтерпретируется в других частях кода, не сломается ли это использование? - person Tom; 19.07.2011
comment
@Tom: это все еще POD. Чтобы класс не был POD, он должен иметь поле экземпляра не-POD, поле экземпляра ссылки, определяемый пользователем деструктор или определяемый пользователем оператор присваивания копии. Обратите внимание, что «оператор присваивания копии» — это не то же самое, что «оператор присваивания», определенный в VlanData.P, поэтому это POD. - person Konstantin Oznobihin; 19.07.2011
comment
Хм... VlanData имеет элемент данных структуры, который не является POD, делает ли это VLanData не POD? Учитывая, что mVlanPriority является новым членом, код, в котором он будет использоваться, в любом случае является новым кодом. Почему предыдущий reinterpret_cast не продолжал работать? вся кодовая база должна быть скомпилирована в любом случае. Везде, где VlanData используется как объединение, если есть какой-либо эффект, он не должен отличаться от эффекта добавления mVlanPriority в качестве типа uint8_t. - person Murali VP; 19.07.2011
comment
@Tom: VlanData в этом случае все еще можно считать POD. Вы можете скопировать его с помощью memcpy(dst, &vlandata, sizeof(VlanData)), и порядок полей в первых байтах не изменится. - person Tugrul Ates; 19.07.2011
comment
+1, круто, очень интересное решение, я не знал, что ты так умеешь. Меня беспокоило то, что с этими дополнительными функциями вы теряете все гарантии в отношении выравнивания класса, но это было только мое предположение - person Tom; 19.07.2011
comment
Эта версия VlanData определенно не является POD, она не является членом POD. P не является POD, поскольку у него есть конструктор. Вполне может быть, что в вашей реализации его можно успешно скопировать с помощью memcpy, но то же самое может быть верно и для версии спрашивающего, у которой есть конструктор. - person Steve Jessop; 19.07.2011
comment
Согласно комментарию вице-президента Мурали, это не POD. VlanData::P очевидно, что нет, поэтому VlanData имеет члена, не являющегося членом POD, и сам не является POD. POD на самом деле просто другое название структуры в стиле C, а C не имеет структур с элементами, инициализированными нулем. - person MSalters; 19.07.2011

Есть ли безопасный способ убедиться, что для mVlanPriority установлено значение 0 в устаревшем коде без обновления устаревшего кода?

Нет, в текущем стандарте нет стандартного способа. У вас должен быть конструктор.

Какое использование класса могло бы сломаться, если бы он больше не был типом POD?

Как упомянул @junjanes в комментариях, ваш код будет сломан, когда вы попытаетесь инициализировать элементы скобками.

Изменить. Чтобы решить вашу проблему, я бы предложил

struct VlanData
{
  uint16_t mEtherType;
  uint16_t mVlanId;
  uint8_t mVlanPriority; // New member

  VlanData(uint16_t ether, uint16_t id, uint8_t priority = 0) :
           mEtherType(ether), mVlanId(id), mVlanPriority(priority)
  {}
};

Итак, теперь ваша новая переменная будет инициализирована как 0, и вам придется меньше печатать, чтобы исправить ошибку компиляции.

Сдача,

VlanData myVlans = { 0x8100, 100 };

To,

VlanData myVlans( 0x8100, 100 );
person iammilind    schedule 19.07.2011

Я не эксперт по С++0x, но я знаю, что строгие гарантии pod-ности были смягчены в c++0x с введением класса standard-layout. Ваш класс с конструктором не pod, но я считаю, что это standard-layout, поэтому, возможно, стоит проверить совместимость ваших компиляторов с этим аспектом нового стандарта. Я думаю, что проблемы, которые у вас есть, были исправлены с помощью c++0x.

Я также полагаю, что инициализация фигурными скобками стандартных классов макета также разрешена в c++0x. См. разделы Initializer lists и Uniform initialization этой статьи в Википедии.

Поиск того, что исправление класса стандартного макета, должен дать довольно хороший список того, что может сломаться для типов, отличных от pod, в текущем стандарте. Например, reinterpret_cast небезопасен в С++ 03 для типов, отличных от pod (выравнивание может быть неправильным), но безопасен в С++ 0x для классов стандартной компоновки.

Короче говоря, я думаю, что ваши разочарования хорошо известны и, возможно, даже сглажены в новом стандарте, но я не думаю, что будет возможно исправить их все с помощью текущего стандарта.

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

namespace version_1_1
{
  struct VlanData
  {
      uint16_t mEtherType;
      uint16_t mVlanId;
      uint8_t mVlanPriority; // New member
  };

  vlanData 
  convert_VlanData( ::VlanData const& v)
  {
     VlanData v2 = {v.mEtherType,v.mVlanId, 0};
     return v2;
  }
}

и тогда вы четко и ясно указываете, когда вы что используете, и переносите вещи, когда вам нужно.

person Tom    schedule 19.07.2011