Полагаться на порядок инициализации

согласно стандарту C++ 14, нестатические переменные-члены инициализируются в том порядке, в котором они объявлены в классе. Сокращенный код ниже полагается на это правило для управления функцией потока.

class foo
{
     foo(): 
          keep_going{true},
          my_thread(&foo::go,this)
     {}

      void go()
      {
          while(keep_going)
             check a std::condition_variable and do some work;
      }
      bool keep_going;
      std::thread my_thread;
}

отметим, что keep_going объявляется перед объектом thread и должно быть установлено значение true к тому времени, когда поток входит в функцию go. Это нормально, и, кажется, работает нормально.

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

1 безопасно ли полагаться на такой порядок инициализации? Мой реальный объект не имеет смысла без потока обработки, поэтому я хочу установить его в конструкторе.

2 небезопасно давать код другим, когда он полагается на относительно неясные вещи, такие как порядок инициализации?

3 ответов


  1. безопасно согласно стандарту.

  2. крайне небезопасно. Немногие люди знают об этом, и кто-то, поддерживающий ваш файл заголовка, может изменить порядок членов с катастрофическими последствиями.

Я бы не стал полагаться на него.


хотя стандартный это безопасно, я бы не пошел с ним.

анекдот: я написал настраиваемый ThreadPool в ОС Windows с помощью visual studio 2013. Я объявил пул потоков глобальным. Конечно!--5-->стандартный глобальные объекты уничтожаются после того, как вернулся. деструктор пула потоков попытался join каждый поток, но увы! врезной замок. (вы можете прочитать об этой проблеме здесь: std:: thread:: join () зависает, если вызывается после выхода main () при использовании VS2012 RC). В стандарте очень четко указано, что если поток является соединяемым, нет проблем с его присоединением, но, как вы можете видеть, это не было реализовано идеально.

Почему я говорю вам об этом несвязанном вопросе? Потому что даже компиляторы и платформы имеют некоторые ошибки. Тонкие вещи могут не быть реализованы на 100% правильно в первых нескольких версиях компилятора с поддержкой соответствующих версий.

вот почему я бы не пошел с этой идеей. Как work-around, я бы объявил нить, завернутую в std::unique_ptr и инициализировать в теле конструктора. Таким образом, нет никаких шансов, что он будет инициализирован до keep_going.

foo(): 
  keep_going{true}
   { my_thread = std::make_unique<std::thread>(&foo::go,this); }

Я хотел бы переписать код, чтобы сделать код понятным даже для Обезьяны.

еще одна потенциальная проблема здесь может возникнуть, когда класс foo базовый класс. Поток начнется с не полностью построенного объекта. Что произойдет, если конструктор производного класса не удается? В этом случае лучше переместить выполнение потока из конструктора в start() метод.