Может ли оператор "будущего безопасного" быть обработан на С++?

Приветствуем всех,

Можно ли создать надежный оператор сравнения (==) в С++?

Проблема, с которой я столкнулся, заключалась в том, что у нас есть класс с несколькими членами. У нас есть оператор сравнения для проверки того, имеет ли экземпляр-1 объекта те же значения, что и экземпляр-2.

то есть. мы можем сделать

class blarg {
    .....
};

.....

blarg   b1(..initializers...);
blarg   b2 = b1;

if (b1 == b2) {
    ... then do something ....
}

Однако у меня был сотрудник, который добавил в класс нового члена, но не смог обновить оператор сравнения. Это приводит к проблемам, которые нам понадобилось время, чтобы понять.

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

Моя первая реакция заключалась в использовании команды memcmp. Однако, прочитав запись для Сравнение структур в C vs С++, я вижу, что это может быть проблематично из-за того, что классы С++ имеют не только данные элемента внутри.

Как другие справляются с этим?

Заранее благодарим вас за помощь.

Ответ 1

Ну, очевидное решение - быть более осторожным, когда вы расширяете исходный класс.:) "Быть ​​более осторожным" включает такие вещи, как обзоры кода, такие вещи, но, очевидно, это не является доказательством дурака.

Таким образом, решение этой проблемы с философской точки зрения, а не технического, часто может дать представление. Философия в этом случае заключается в том, чтобы быть параноидальным программистом. Предположим, что код, который вы пишете сегодня, будет разбит на несколько месяцев или лет спустя. (Изменить на @Noah комментарии ниже: Чаще всего, это nitwit - это я. Будучи параноидным программистом, я защищаю себя от себя, вероятно, больше, чем кто-либо другой.) Если вы можете сделать что-то, чтобы убедиться, что когда nitwit нарушит ваш код, что-то не получается прежде чем продукт будет отправлен, что поможет.

Две вещи, которые мне нравятся, - это статические утверждения и модульные тесты. Статическое утверждение может использоваться в вашем коде operator==, чтобы убедиться, что sizeof ваш класс - это то, что вы ожидаете от него. Например:

bool MyClass::operator==(const MyClass& rhs) const
{
  static_assert(sizeof(MyClass) == sizeof(foo_) + sizeof(bar_))
  ...
}

... где foo_ и bar_ являются переменными-членами. Это нарушит компиляцию при изменении размера класса.

Как статическое утверждение обычно принимает форму класса шаблона, который не сможет скомпилироваться, когда выражение ложно. Это может быть немного сложнее написать (но это интересное упражнение - подумайте, что произойдет, если вы попытаетесь добавить член char test_[0] в класс). К счастью, это колесо уже было изобретено. См. Boost для примера, и я думаю, что новый компилятор MSVC также поставляется вместе с ним.

Ответ 2

ОК, настоящий ответ.

Собственный unit test для вашего объекта должен полностью перевернуть эту ошибку. Это именно то, что они умеют указывать.

Ответ 3

Единственный реальный способ решить это - не обязательно иметь оператор присваивания для этого класса вообще. Что это, спросите вы? Что делать, если у меня есть динамическая память и т.д., Что требует глубокой копии?

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

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

Ответ 4

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

Для нормальных классов это на самом деле не вариант, но я бы порекомендовал провести единичные тесты для этого, как отметил в своем ответе Ной Робертс.

Ответ 5

Может показаться глупым, но вы можете написать список всех вещей, которые (возможно) должны учитывать все члены класса. Я могу думать:

  • определяемые пользователем конструкторы (в частности, их списки инициализаторов)
  • destructor (если ваш класс когда-либо использует членов, которые в нем нуждаются, хотя если деструктор должен учитывать более одного члена, у вас наверняка есть проблемы)
  • swap
  • operator<<(std::ostream&, const blarg&) или другие формы (де) сериализации
  • operator<
  • operator==
  • Функция [С++ 0x] hash
  • возможно, что-то еще, что я забыл.

Запуск контрольного списка при добавлении члена или изменение типа или значения члена (или, возможно, его удаление), но в этом случае что-либо, использующее его, как правило, взрывается сам по себе, и все, что его не использует, вероятно, t заметили, что оно ушло).

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

Очевидно, если вы справитесь с этим, вы будете следовать принципу Open/Closed, и этого никогда не произойдет. Лично мне это никогда не удалось.