Ctor Initializer: самоинициализация вызывает сбой? - PullRequest
2 голосов
/ 08 октября 2010

Мне было трудно отлаживать сбой на производстве. Просто хотел подтвердить с людьми здесь о семантике. У нас есть класс, как ...

class Test {
public:
  Test()
  {
    // members initialized ...
    m_str = m_str;
  }
  ~Test() {}
private:
  // other members ...
  std::string m_str;
};

Кто-то изменил инициализацию, чтобы использовать списки инициализации ctor, что является достаточно правильным в нашей семантике кода. Порядок инициализации и их начальное значение корректны среди прочего. Так класс выглядит как ...

class Test {
public:
  Test() 
    : /*other inits ,,, */ m_str(m_str)
  {
  }
  ~Test() {}
private:
  // other members ...
  std::string m_str;
};

Но код неожиданно начал падать! Я выделил длинный список единиц в этот кусок кода m_str(m_str). Я подтвердил это через текст ссылки .

Должен ли он падать? Что стандарт говорит об этом? (Это неопределенное поведение?)

Ответы [ 5 ]

13 голосов
/ 08 октября 2010

Первый конструктор эквивалентен

  Test()
  : m_str()
  {
    // members initialized ...
    m_str = m_str;
  }

, то есть к тому времени, когда вы получите присваивание в конструкторе, m_str уже неявно инициализировано для пустогострока.Таким образом, присваивание self, хотя и совершенно бессмысленно и излишне, не вызывает проблем (поскольку std::string::operator=(), как должен делать любой хорошо написанный оператор присваивания, проверяет самопредставление и ничего не делает в этом случае).

Однако ввторой конструктор, вы пытаетесь инициализировать m_str самим собой в списке инициализаторов - в этот момент он еще не инициализирован .Таким образом, результатом является неопределенное поведение.

Обновление: Для примитивных типов это все еще неопределенное поведение (в результате получается поле со значением мусора), но оно не падает (обычно - см.комментарии ниже для исключений), поскольку примитивные типы по определению не имеют конструкторов, деструкторов и не содержат указателей на другие объекты.

То же самое верно для любого типа, который не содержит членов-указателей с семантикой владения.std::string настоящим подтверждается, что он не является одним из них: -)

2 голосов
/ 08 октября 2010

Первоначальная «инициализация» по присваиванию совершенно излишня.

Это не принесло никакого вреда, кроме траты процессорных циклов, потому что на момент назначения член m_str уже был инициализирован,default.

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

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

К сожалению, проблема, с которой вы столкнулись, не является технической, она гораздо более фундаментальная.Это похоже на то, как работник автомобильного завода ставит вопрос о квадратных колесах, которые они ставят на новую автомобильную марку.Тогда проблема не в том, что квадратные колеса не работают, а в том, что большое количество инженеров и менеджеров были вовлечены в решение использовать причудливые квадратные колеса, и никто из них не возражал - некоторые из них, несомненно, не сработали.Я не понимаю, что квадратные колеса не работают, но большинство из них, я подозреваю, просто боялись сказать, в чем они были уверены на 100%.Так что это, скорее всего, проблема управления.Извините, но я не знаю, как это исправить ...

2 голосов
/ 08 октября 2010

m_str является созданным в списке инициализации.Следовательно, в то время, когда вы назначаете его себе, он не полностью создан.Следовательно, неопределенное поведение.

(Что такое самоопределение в любом случае должно делать?)

1 голос
/ 08 октября 2010

Неопределенное поведение не должно приводить к сбою - оно может делать практически все: от продолжения работы, как будто не было никаких проблем, до мгновенного сбоя, до выполнения чего-то действительно странного, вызывающего, казалось бы, не связанные проблемы потом. Каноническое утверждение состоит в том, что это заставляет «демонов вылетать из носа» (иначе, «вызывает носовых демонов»). Когда-то у изобретателя этой фазы был (довольно крутой) веб-сайт, рассказывающий о ядерной войне, которая началась с того, кто вызвал неопределенное поведение в «DeathStation 9000».

Редактировать: Точная формулировка из стандарта (§: 1.3.12):

1.3.12 неопределенное поведение [defns.undefined]

поведение, которое может возникнуть при использовании ошибочной программной конструкции или ошибочных данных, для которых это Международный стандарт не предъявляет никаких требований. Неопределенное поведение также может ожидаться, когда это Международный стандарт опускает описание любого явного определения поведения. [Примечание: допустимо неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами до поведения во время перевод или выполнение программы документированным образом, характерным для среды (с или без выдача диагностического сообщения), чтобы прекратить перевод или выполнение (с выдачей диагностическое сообщение).

0 голосов
/ 08 октября 2010

Это такая же разница, как между

std::string str;
str = str;

и

std::string str(str);

Первый работает (хотя это бессмыслица), второй - нет, поскольку он пытается скопировать-построить объект из еще не построенного объекта.

Конечно, путь будет

Test() : m_str() {}
...