Предотвратить нежелательное преобразование в конструкторе

Согласно здесь, explicit:

Определяет конструкторы и операторы преобразования (начиная с C++11), которые не допускают неявные преобразования или инициализацию копирования.

Таким образом, эти две техники идентичны?

struct Z {
        // ...
        Z(long long);     // can initialize with a long long
        Z(long) = delete; // but not anything smaller
};

struct Z {
        // ...
        explicit Z(long long);     // can initialize ONLY with a long long
};

5 ответов

Решение

Они не идентичны.

Z z = 1LL;

Вышесказанное работает с неявной версией, но не с явной версией.

Объявив конструктор Z явный не предотвращает преобразование аргумента конструктора из другого типа. Это предотвращает преобразование аргумента в Z без вызова конструктора явно.

Ниже приведен пример явного вызова конструктора.

Z z = Z(1LL);

Нет, они не одинаковы. explicit запрещает неявные преобразования в этот тип, если выбран этот конструктор - неявные преобразования в аргументах не имеют значения. delete запрещает любую конструкцию, если этот конструктор выбран, и может использоваться для запрета неявного преобразования аргумента.

Так, например:

struct X {
    explicit X(int ) { }
};

void foo(X ) { }

foo(4);      // error, because X's constructor is explicit
foo(X{3});   // ok
foo(X{'3'}); // ok, this conversion is fine

Это отдельно от deleteконструктор:

struct Y {
    Y(int ) { }
    Y(char ) = delete;
};

void bar(Y ) { }

bar(4);      // ok, implicit conversion to Y since this constructor isn't explicit
bar('4');    // error, this constructor is deleted
bar(Y{'4'}); // error, doesn't matter that we're explicit

Эти две техники также являются ортогональными. Если вы хотите, чтобы тип не был неявно конвертируемым и конструируемым только из int, вы можете сделать оба:

struct W {
    explicit W(int ) { }

    template <class T>
    W(T ) = delete;
};

void quux(W );

quux(4);      // error, constructor is explicit
quux('4');    // error, constructor is deleted
quux(4L);     // error, constructor is deleted
quux(W{'4'}); // error, constructor is deleted
quux(W{5});   // ok

explicit блокирует неявное преобразование в ваш тип.

Ваш =delete техника блокирует неявное преобразование из long в long long,

Это почти не связано.

Есть 4 случая, которые иллюстрируют разницу:

Z z = 1L;
Z z = 1LL;

это неявное преобразование из long а также long long в Z,

Z z = Z(1L);
Z z = Z(1LL);

явное преобразование из long а также long long в Z,

explicit Z(long long) блоки:

Z z = 1L;
Z z = 1LL;

в то время как Z(long)=delete блоки:

Z z = 1L;
Z z = Z(1L);

explicit Z(long long) позволяет Z z = Z(1L) потому что преобразование из long в long long неявно, но не связано с явным преобразованием в Z это происходит потом.

Обратите внимание, что смесь explicit а также =delete только листья Z z=Z(1LL) как действительный среди ваших 4 версий.

(вышеизложенное предполагает действительную копию или перемещение ctor; если нет, замените Z z=Z(...) с Z z(...) и результат тот же вывод).

struct Zb {
        Zb(long long)
        {};     // can initialize with a long long
        Zb(long) = delete; // but not anything smaller
    };

struct Za {
        // ...
        explicit Za(long long)
        {};     // can initialize ONLY with a long long
    };

int main()
{
    Za((long long)10);  // works
    Za((long)10);       // works    

    Zb((long long)10);  // works
    Zb((long)10);       // does not work

    return 0;
}

Ваш пример требует явного удаления.

Live: http://cpp.sh/4sqb

Они не то же самое.

Из стандартного рабочего проекта n4296:

12.3.1 - [class.conv.ctor]:
1 Конструктор, объявленный без явного спецификатора функции, определяет преобразование типов его параметров в тип своего класса. Такой конструктор называется конвертирующим конструктором.

2 Явный конструктор создает объекты точно так же, как неявные конструкторы, но делает это только там, где синтаксис прямой инициализации (8.5) или где явно используются приведения (5.2.9, 5.4). Конструктор по умолчанию может быть явным конструктором; такой конструктор будет использоваться для выполнения инициализации по умолчанию или инициализации значения (8.5).

Далее следует пример каждого из них:

struct X {
    X(int);
    X(const char*, int =0);
    X(int, int);
};

void f(X arg) {
    X a = 1;        // a = X(1)
    X b = "Jessie"; // b = X("Jessie",0)
    a = 2;          // a = X(2)
    f(3);           // f(X(3))
    f({1, 2});      // f(X(1,2))
}

С явным конструктором:

struct Z {
    explicit Z();
    explicit Z(int);
    explicit Z(int, int);
};

Z a;                      // OK: default-initialization performed
Z a1 = 1;                 // error: no implicit conversion
Z a3 = Z(1);              // OK: direct initialization syntax used
Z a2(1);                  // OK: direct initialization syntax used
Z* p = new Z(1);          // OK: direct initialization syntax used
Z a4 = (Z)1;              // OK: explicit cast used
Z a5 = static_cast<Z>(1); // OK: explicit cast used
Z a6 = { 3, 4 };          // error: no implicit conversion
Другие вопросы по тегам