Полагаться на порядок инициализации
согласно стандарту 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 ответов
безопасно согласно стандарту.
крайне небезопасно. Немногие люди знают об этом, и кто-то, поддерживающий ваш файл заголовка, может изменить порядок членов с катастрофическими последствиями.
Я бы не стал полагаться на него.
хотя стандартный это безопасно, я бы не пошел с ним.
анекдот: я написал настраиваемый 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()
метод.