Примеры прямого-списка-инициации против копирования-списка-инициации против копирования-инициации из первоначальных различий, начиная с C ++ 17 - PullRequest
1 голос
/ 28 апреля 2020

Для этих 3 форм инициализации (для некоторого типа A)

A x = {<...>};
A x{<...>};
auto x = A{<...>};

, что было бы примерами различий в поведении начиная с C ++ 17, за исключением разницы по сравнению с явными ctors (явный ctor сломал бы первый)?

(Так как гарантированное копирование исключается из C ++ 17, копирование неявным образом; неявные преобразования конструктора копирования здесь также не должны применяться из-за auto ...)

Я в целом знаю, как работают формы инициализации C ++, извините за широту / неопределенность вопроса, мне просто интересно, есть ли какие-либо (существенные) различия между тремя вышеуказанными формами, или, может быть, они используются в основном взаимозаменяемо (кроме упомянутая разница).

1 Ответ

4 голосов
/ 28 апреля 2020

Я думаю, что различия здесь важны, особенно из-за неявного / явного различия, которое, я полагаю, вы слишком быстро игнорируете.

Я собираюсь ответить на вопрос, несколько иной, чем тот, который вы спросил: зачем использовать какую форму? В отличие от: В чем различия?

A x = {<...>};

Предпочитаете эту форму, если она компилируется.

Она не будет компилироваться, если выражение rhs имеет тип, который явно преобразуется в A. И это очень хорошая вещь! Это функция безопасности, предоставляемая языком.

Авторы типа обычно (или, по крайней мере, должны) резервируют неявное конструирование / преобразование для очень безопасных преобразований. Такие преобразования должны быть без потерь и не должны изменять фундаментальное семантическое значение выражения между правой и левой частями. Например: milliseconds x = 3s; rhs - seconds, а lhs - milliseconds. И конструкция преобразуется между двумя временными интервалами и не теряет никакой информации вообще. Это хорошее место для использования этой формы построения.

Используя эту форму, программист говорит: «Просто дайте мне« безопасный набор »конструкторов для типа A». Если в моем типе выражения есть ошибка в rhs, которая может привести к небезопасному преобразованию, я хотел бы узнать об этом во время компиляции.

Когда A является целочисленным типом, тогда даже в том числе {} имеет преимущество:

unsigned x = {i};

Это соответствует стилю высказывания, просто дайте мне безопасные преобразования. Но это добавляет дополнительный пояс к вашим подтяжкам: просто дайте мне преобразования, которые не будут сужаться. {} - это в основном дополнительный пробел в последнем C ++, чтобы компенсировать ошибки проектирования, совершенные за C несколько десятилетий go.


Однако иногда вам нужен больший молот. Иногда явные преобразования - это то, что вам нужно и нужно. Это время для:

A x{<...>};

Например: milliseconds x{3}; Преобразует int в millisecond. Хотя преобразование без потерь, тип lhs не похож на тип rhs. Rhs может представлять 3 что угодно. 3 яблока. 3 IRS уведомления. 3 года. Это не безопасное преобразование, чтобы сделать неявно. И std :: lib это знает. Если бы вы попытались, он не скомпилируется. Тем не менее, иногда это именно то, что вам нужно сделать. Зарезервируйте эту форму для этой ситуации.

Несоблюдение этого совета может в некоторых ситуациях привести к ошибкам во время выполнения. Два из которых продемонстрированы в этом молниеносном разговоре .


Наконец, это хорошая форма:

auto x = A{<...>}; 

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

Мой любимый пример использования этой формы в реализации std::chrono::round:

template <class To, class Rep, class Period>
constexpr
To
round(const duration<Rep, Period>& d)
{
    auto t0 = floor<To>(d);
    auto t1 = t0 + To{1};
    if (t1 == To{0} && t0 < To{0})
        t1 = -t1;
    auto diff0 = d - t0;  // here
    auto diff1 = t1 - d;  // and here
    if (diff0 == diff1)
    {
        if (t0 - duration_cast<To>(t0/2)*2 == To{0})
            return t0;
        return t1;
    }
    if (diff0 < diff1)
        return t0;
    return t1;
}

Строки, помеченные «здесь и здесь», приводят (в общем) к очень сложному типу для diff0 и diff1: есть пара различных способов его написания. Это common_type_v<duration<Rep, Period>, To>. И действительно, для читателя кода не важно, что это за тип. Единственная важная вещь, которую нужно знать, - это то, что этот тип будет представлять точное различие в двух операндах.


Таким образом, это не «лучшая форма». Они все хорошие инструменты, чтобы иметь в вашем наборе инструментов. И хитрость заключается в том, чтобы знать, когда использовать. И если вы справитесь с этим, вы будете более опытными, чем подавляющее большинство программистов на C ++.

...