Читать «Эффективный и современный С++. 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 (со знаком равенства), соответствует инициализации копированием (сору initialization). Синтаксис же, использованный для инициализации r2 (с круглыми скобками, хотя могут использоваться и фигурные), дает то, что называется прямой инициализацией (direct initialization). Инициализация копированием не может использовать конструкторы, объявленные как explicit, в то время как прямая инициализация — может. Вот почему строка с инициализацией r1 не компилируется, в отличие от строки с инициализацией r2.

Но вернемся к push_back и emplace_back и в более общем случае — к функциям вставки и размещения. Функции размещения используют прямую инициализацию, т.e. могут пользоваться конструкторами, объявленными как explicit. Функции вставки применяют инициализацию копированием, а потому использовать такие конструкторы не могут.

regexes.emplace_back(nullptr); // Компилируется. Прямая

    // инициализация разрешает использовать конструктор

    // explicit std::regex, получающий указатель

regexes.push_back(nullptr); // Ошибка! Копирующая

    // инициализация такие конструкторы не использует

Урок, который следует извлечь из данного материала, состоит в том, что при использовании размещающих функций необходимо быть особенно осторожным и убедиться, что функциям передаются правильные аргументы, поскольку в ходе анализа кода будут рассмотрены даже конструкторы, объявленные как explicit.

Следует запомнить

• В принципе, функции размещения должны иногда быть более эффективными, чем соответствующие функции вставки, и не должны быть менее эффективными.

• На практике они чаще всего более быстрые, когда (1) добавляемое значение конструируется в контейнере, а не присваивается; (2) типы передаваемых аргументов отличаются от типа, хранящегося в контейнере; и (3) контейнер не отвергает дубликаты уже содержащихся в нем значений.

• Функции размещения могут выполнять преобразования типов, отвергаемые функциями вставки.