Читать «Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14» онлайн - страница 222
Скотт Мейерс
std::regex r = nullptr; // Ошибка! Не компилируется!
regexes.push_back(nullptr); // Ошибка! Не компилируется!
В обоих случаях требуется неявное преобразование указателя в std::regex
, а объявление конструктора как explicit
его предотвращает.
Однако в вызове emplace_back
мы передаем не объект std::regex
, а std::regex
. Это не рассматривается как запрос неявного преобразования. Компилятор трактует этот код так, как если бы вы написали
std::regex r(nullptr); // Компилируется
Если лаконичный комментарий “Компилируется” кажется вам лишенным энтузиазма, то это хорошо, потому что, несмотря на компилируемость, данный код имеет неопределенное поведение. Конструктор std::regex
, принимающий указатель const char*
, требует, чтобы этот указатель был ненулевым, а nullptr подчеркнуто нарушает данное требование. Если вы напишете и скомпилируете такой код, лучшее, на что вы можете надеяться, — аварийное завершение программы во время выполнения. Если вы не такой счастливчик, то вам предстоит получить немалый опыт работы с отладчиком.
На минутку оставляя без внимания push_back
и emplace_back
, обратим внимание на то, что очень похожие синтаксисы инициализации дают совершенно разные результаты:
std::regex r1 = nullptr; // Ошибка! Не компилируется
std::regex r2(nullptr); // Компилируется
В официальной терминологии стандарта синтаксис, использованный для инициализации r1
(со знаком равенства), соответствует r2
(с круглыми скобками, хотя могут использоваться и фигурные), дает то, что называется explicit
, в то время как прямая инициализация — может. Вот почему строка с инициализацией r1
не компилируется, в отличие от строки с инициализацией r2
.
Но вернемся к push_back
и emplace_back
и в более общем случае — к функциям вставки и размещения. Функции размещения используют прямую инициализацию, т.e. могут пользоваться конструкторами, объявленными как explicit
. Функции вставки применяют инициализацию копированием, а потому использовать такие конструкторы не могут.
regexes.emplace_back(nullptr); // Компилируется. Прямая
// инициализация разрешает использовать конструктор
// explicit std::regex, получающий указатель
regexes.push_back(nullptr); // Ошибка! Копирующая
// инициализация такие конструкторы не использует
Урок, который следует извлечь из данного материала, состоит в том, что при использовании размещающих функций необходимо быть особенно осторожным и убедиться, что функциям передаются правильные аргументы, поскольку в ходе анализа кода будут рассмотрены даже конструкторы, объявленные как explicit
.
Следует запомнить
• В принципе, функции размещения должны иногда быть более эффективными, чем соответствующие функции вставки, и не должны быть менее эффективными.
• На практике они чаще всего более быстрые, когда (1) добавляемое значение конструируется в контейнере, а не присваивается; (2) типы передаваемых аргументов отличаются от типа, хранящегося в контейнере; и (3) контейнер не отвергает дубликаты уже содержащихся в нем значений.
• Функции размещения могут выполнять преобразования типов, отвергаемые функциями вставки.