Что означает ключевое слово explicit в С++?
Что означает явное ключевое слово?
Ответ 1
Компилятору разрешено выполнять одно неявное преобразование для разрешения параметров функции. Это означает, что компилятор может использовать конструкторы, вызываемые с помощью одиночного параметра для преобразования из одного типа в другой, чтобы получить правильный тип для параметра.
Здесь примерный класс с конструктором, который может использоваться для неявных преобразований:
class Foo
{
public:
  // single parameter constructor, can be used as an implicit conversion
  Foo (int foo) : m_foo (foo) 
  {
  }
  int GetFoo () { return m_foo; }
private:
  int m_foo;
};
Здесь простая функция, которая принимает объект Foo:
void DoBar (Foo foo)
{
  int i = foo.GetFoo ();
}
и здесь, где вызывается функция DoBar.
int main ()
{
  DoBar (42);
}
Аргумент не является объектом Foo, а <<26 > . Однако существует конструктор для Foo, который принимает int, поэтому этот конструктор может быть использован для преобразования параметра в правильный тип.
Компилятору разрешено делать это один раз для каждого параметра.
Префикс ключевого слова explicit конструктору запрещает компилятору использовать этот конструктор для неявных преобразований. Добавление его в класс выше приведет к ошибке компилятора при вызове функции DoBar (42). Теперь необходимо явно вызвать преобразование с помощью DoBar (Foo (42))
Причина, по которой вы захотите сделать это, - это избежать случайного построения, которое может скрыть ошибки. Продуманный пример:
-  У вас есть класс MyString(int size)с конструктором, который строит строку заданного размера. У вас есть функцияprint(const MyString&), и вы вызываетеprint(3)(когда вы на самом деле намеревались позвонитьprint("3")). Вы ожидаете, что он напечатает "3", но вместо этого печатает пустую строку длиной 3.
Ответ 2
Предположим, у вас есть класс String:
class String {
public:
    String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};
Теперь, если вы попробуете:
String mystring = 'x';
Символ 'x' будет неявно преобразован в int, а затем будет вызываться конструктор String(int). Но это не то, что пользователь мог бы намереваться. Итак, чтобы предотвратить такие условия, мы будем определять конструктор как explicit:
class String {
public:
    explicit String (int n); //allocate n bytes
    String(const char *p); // initialize sobject with string p
};
Ответ 3
В С++ конструктор с одним обязательным параметром считается неявной функцией преобразования. Он преобразует тип параметра в тип класса. Является ли это хорошей вещью или нет, зависит от семантики конструктора.
Например, если у вас есть строковый класс с конструктором String(const char* s), возможно, именно то, что вы хотите. Вы можете передать const char* функции, ожидающей String, и компилятор автоматически создаст для вас временный объект String.
С другой стороны, если у вас есть класс буфера, конструктор Buffer(int size) принимает размер буфера в байтах, вы, вероятно, не хотите, чтобы компилятор спокойно превращал int в Buffer s. Чтобы предотвратить это, вы объявляете конструктор с ключевым словом explicit:
class Buffer { explicit Buffer(int size); ... }
Таким образом,
void useBuffer(Buffer& buf);
useBuffer(4);
становится ошибкой времени компиляции. Если вы хотите передать временный объект Buffer, вы должны сделать это явно:
useBuffer(Buffer(4));
Итак, если ваш однопараметрический конструктор преобразует параметр в объект вашего класса, вы, вероятно, не хотите использовать ключевое слово explicit. Но если у вас есть конструктор, который просто принимает один параметр, вы должны объявить его как explicit, чтобы компилятор не удивил вас неожиданными преобразованиями.
Ответ 4
Этот ответ касается создания объекта с/без явного конструктора, поскольку он не рассматривается в других ответах.
Рассмотрим следующий класс без явного конструктора:
class Foo
{
public:
    Foo(int x) : m_x(x)
    {
    }
private:
    int m_x;
};
Объекты класса Foo могут быть созданы двумя способами:
Foo bar1(10);
Foo bar2 = 20;
 В зависимости от реализации второй способ создания экземпляра класса Foo может быть запутанным, или не то, что планировал программист. Префикс explicit ключевого слова конструктору генерирует ошибку компилятора в Foo bar2 = 20; ,
 Обычно хорошей практикой является объявление конструкторов с одним аргументом как explicit, если только ваша реализация не запрещает его.
Заметим также, что конструкторы с
- аргументы по умолчанию для всех параметров или
- аргументы по умолчанию для второго параметра
 оба могут использоваться как конструкторы с одним аргументом. Поэтому вы можете сделать это explicit.
 Например, если вы намеренно не хотите, чтобы ваш конструктор с одним аргументом был явным, это если вы создаете функтор (посмотрите на структуру add_x, объявленную в этом ответе). В этом случае создается объект как add_x add30 = 30; вероятно, имеет смысл.
Вот хорошая запись о явных конструкторах.
Ответ 5
Ключевое слово explicit делает конструктор преобразования конструктором без преобразования. В результате код менее подвержен ошибкам.
Ответ 6
 Ключевое слово explicit сопровождает либо
- конструктор класса X, который нельзя использовать для неявного преобразования первого (любого только) параметра в тип X
С++ [class.conv.ctor]
1) Конструктор, объявленный без явного спецификатора функции, указывает преобразование из типов его параметров в тип своего класса. Такой конструктор называется конструктором преобразования.
2) Явный конструктор строит объекты так же, как и неявные конструкторы, но делает это только там, где явно используется синтаксис прямой инициализации (8.5) или где используются броски (5.2.9, 5.4). Конструктор по умолчанию может быть явным конструктором; такой конструктор будет использоваться для выполнения инициализации по умолчанию или инициализации значения (8.5).
- или функция преобразования, которая рассматривается только для прямой инициализации и явного преобразования.
С++ [class.conv.fct]
2) Функция преобразования может быть явной (7.1.2), и в этом случае она рассматривается только как пользовательское преобразование для прямой инициализации (8.5). В противном случае определяемые пользователем преобразования не ограничиваются использованием в назначениях и инициализация.
Обзор
Явные функции преобразования и конструкторы могут использоваться только для явных преобразований (прямая инициализация или явная операция литья), в то время как неявные конструкторы и функции преобразования могут использоваться как для явных, так и для явных преобразований.
/*
                                 explicit conversion          implicit conversion
 explicit constructor                    yes                          no
 constructor                             yes                          yes
 explicit conversion function            yes                          no
 conversion function                     yes                          yes
*/
 Пример использования структур X, Y, Z и функций foo, bar, baz:
Посмотрите на небольшую настройку структур и функций, чтобы увидеть разницу между преобразованиями explicit и не explicit.
struct Z { };
struct X { 
  explicit X(int a); // X can be constructed from int explicitly
  explicit operator Z (); // X can be converted to Z explicitly
};
struct Y{
  Y(int a); // int can be implicitly converted to Y
  operator Z (); // Y can be implicitly converted to Z
};
void foo(X x) { }
void bar(Y y) { }
void baz(Z z) { }
Примеры конструктора:
Преобразование аргумента функции:
foo(2);                     // error: no implicit conversion int to X possible
foo(X(2));                  // OK: direct initialization: explicit conversion
foo(static_cast<X>(2));     // OK: explicit conversion
bar(2);                     // OK: implicit conversion via Y(int) 
bar(Y(2));                  // OK: direct initialization
bar(static_cast<Y>(2));     // OK: explicit conversion
Инициализация объекта:
X x2 = 2;                   // error: no implicit conversion int to X possible
X x3(2);                    // OK: direct initialization
X x4 = X(2);                // OK: direct initialization
X x5 = static_cast<X>(2);   // OK: explicit conversion 
Y y2 = 2;                   // OK: implicit conversion via Y(int)
Y y3(2);                    // OK: direct initialization
Y y4 = Y(2);                // OK: direct initialization
Y y5 = static_cast<Y>(2);   // OK: explicit conversion
Примеры функций преобразования:
X x1{ 0 };
Y y1{ 0 };
Преобразование аргумента функции:
baz(x1);                    // error: X not implicitly convertible to Z
baz(Z(x1));                 // OK: explicit initialization
baz(static_cast<Z>(x1));    // OK: explicit conversion
baz(y1);                    // OK: implicit conversion via Y::operator Z()
baz(Z(y1));                 // OK: direct initialization
baz(static_cast<Z>(y1));    // OK: explicit conversion
Инициализация объекта:
Z z1 = x1;                  // error: X not implicitly convertible to Z
Z z2(x1);                   // OK: explicit initialization
Z z3 = Z(x1);               // OK: explicit initialization
Z z4 = static_cast<Z>(x1);  // OK: explicit conversion
Z z1 = y1;                  // OK: implicit conversion via Y::operator Z()
Z z2(y1);                   // OK: direct initialization
Z z3 = Z(y1);               // OK: direct initialization
Z z4 = static_cast<Z>(y1);  // OK: explicit conversion
 Зачем использовать функции преобразования или конструкторы explicit?
Конструкторы преобразования и неявные функции преобразования могут вводить двусмысленность.
Рассмотрим структуру V, конвертируемую в int, структуру U неявно конструктивную из V и функцию f, перегруженную для U и bool соответственно.
struct V {
  operator bool() const { return true; }
};
struct U { U(V) { } };
void f(U) { }
void f(bool) {  }
Вызов f неоднозначен при передаче объекта типа V.
V x;
f(x);  // error: call of overloaded 'f(V&)' is ambiguous
Компилятор не знает, использовать конструктор U или функцию преобразования, чтобы преобразовать объект V в тип для перехода к f.
Если либо конструктор U, либо функция преобразования V будет explicit, то не будет никакой двусмысленности, поскольку будет рассмотрено только неявное преобразование. Если оба они явны, вызов f с использованием объекта типа V должен выполняться с использованием явного преобразования или операции литья.
Конструкторы преобразования и неявные функции преобразования могут привести к неожиданному поведению.
Рассмотрим функцию печати некоторого вектора:
void print_intvector(std::vector<int> const &v) { for (int x : v) std::cout << x << '\n'; }
Если размер-конструктор вектора не был бы явным, можно было бы вызвать такую функцию:
print_intvector(3);
Что можно ожидать от такого звонка? Одна строка содержит 3 или три строки, содержащие 0? (Где вторая происходит, что происходит.)
Использование явного ключевого слова в интерфейсе класса принуждает пользователя интерфейса явно указывать требуемое преобразование.
Как пишет Бьярне Страуструп (в "Язык программирования на С++", 4-е изд., 35.2.1, стр. 1011), вопрос о том, почему std::duration не может быть неявно построено из простого числа:
Если вы знаете, что вы имеете в виду, будьте откровенны в этом.
Ответ 7
 explicit -keyword может использоваться для принудительного вызова конструктора, который будет вызываться явно.
class C{
public:
    explicit C(void) = default;
};
int main(void){
    C c();
    return 0;
}
 explicit -keyword перед конструктором C(void) сообщает компилятору, что разрешен только явный вызов этого конструктора.
Клавиша explicit -keyword также может использоваться в пользовательских операциях приведения типов:
class C{
public:
    explicit inline operator bool(void) const{
        return true;
    }
};
int main(void){
    C c;
    bool b = static_cast<bool>(c);
    return 0;
}
Здесь explicit -keyword применяет только явные приведения к действию, поэтому bool b = c; в этом случае будет недопустимым. В подобных ситуациях explicit -keyword может помочь программисту избежать неявных, непреднамеренных бросков. Это использование стандартизировано в С++ 11.
Ответ 8
Это уже обсуждалось (то, что является явным конструктором). Но я должен сказать, что ему не хватает подробных описаний, найденных здесь.
Кроме того, всегда хорошая практика кодирования заключается в том, чтобы сделать ваши собственные конструкторы аргументов (включая те, которые имеют значения по умолчанию для arg2, arg3,...), как уже было сказано. Как всегда с С++: если вы этого не сделаете - вы пожелаете, чтобы вы сделали...
Еще одна хорошая практика для классов - сделать создание копии и присвоение частным (a.k.a. отключить ее), если вам действительно не нужно ее реализовать. Это позволяет избежать возможных копий указателей при использовании методов, которые С++ создаст для вас по умолчанию. Другой способ сделать это - получить boost: noncopyable.
Ответ 9
Ссылка Cpp всегда полезна!!! Подробности о явном спецификаторе можно найти здесь. Возможно, вам придется просмотреть неявные преобразования и copy-initialization.
Быстрый просмотр
Явный спецификатор указывает, что конструктор или функция преобразования (поскольку С++ 11) не допускают неявных преобразований или инициализации копии.
Пример следующим образом:
struct A
{
    A(int) { }      // converting constructor
    A(int, int) { } // converting constructor (C++11)
    operator bool() const { return true; }
};
struct B
{
    explicit B(int) { }
    explicit B(int, int) { }
    explicit operator bool() const { return true; }
};
int main()
{
    A a1 = 1;      // OK: copy-initialization selects A::A(int)
    A a2(2);       // OK: direct-initialization selects A::A(int)
    A a3 {4, 5};   // OK: direct-list-initialization selects A::A(int, int)
    A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
    A a5 = (A)1;   // OK: explicit cast performs static_cast
    if (a1) cout << "true" << endl; // OK: A::operator bool()
    bool na1 = a1; // OK: copy-initialization selects A::operator bool()
    bool na2 = static_cast<bool>(a1); // OK: static_cast performs direct-initialization
//  B b1 = 1;      // error: copy-initialization does not consider B::B(int)
    B b2(2);       // OK: direct-initialization selects B::B(int)
    B b3 {4, 5};   // OK: direct-list-initialization selects B::B(int, int)
//  B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
    B b5 = (B)1;   // OK: explicit cast performs static_cast
    if (b5) cout << "true" << endl; // OK: B::operator bool()
//  bool nb1 = b2; // error: copy-initialization does not consider B::operator bool()
    bool nb2 = static_cast<bool>(b2); // OK: static_cast performs direct-initialization
}
Ответ 10
Явные конструкторы преобразования (только для С++)
Явный спецификатор функций управляет нежелательным неявным типом преобразования. Его можно использовать только в объявлениях конструкторов в декларации класса. Например, кроме конструктора, конструкторы в следующем классе являются преобразованием Конструкторы.
class A
{
public:
    A();
    A(int);
    A(const char*, int = 0);
};
Следующие объявления являются законными:
A c = 1;
A d = "Venditti";
Первое объявление эквивалентно A c = A( 1 );.
Если объявить конструктор класса как explicit, предыдущие объявления были бы незаконными.
Например, если вы объявите класс как:
class A
{
public:
    explicit A();
    explicit A(int);
    explicit A(const char*, int = 0);
};
Вы можете назначать только значения, соответствующие значениям типа класса.
Например, следующие утверждения являются законными:
  A a1;
  A a2 = A(1);
  A a3(1);
  A a4 = A("Venditti");
  A* p = new A(1);
  A a5 = (A)1;
  A a6 = static_cast<A>(1);
Ответ 11
Конструкторы добавляют неявное преобразование. Чтобы подавить это неявное преобразование, требуется объявить конструктор с явным параметром.
В С++ 11 вы также можете указать "тип оператора()" с таким ключевым словом http://en.cppreference.com/w/cpp/language/explicit. С такой спецификацией вы можете использовать оператор с точки зрения явных преобразований и прямая инициализация объекта.
PS При использовании преобразований, определенных пользователем USER (через конструкторы и оператор преобразования типов), допускается использование только одного уровня неявных преобразований. Но вы можете комбинировать эти преобразования с другими языковыми преобразованиями
- (char to int, float to double);
- стандартные преобразования (int double);
- конвертировать указатели объектов в базовый класс и void *;
