Что означает явное ключевое слово?

Что означает ключевое слово explicit в C ++?


person Skizz    schedule 23.09.2008    source источник
comment
Я просто хочу указать на то, что со времен C ++ 11 explicit можно применять не только к конструкторам. Теперь он применим и к операторам преобразования. Допустим, у вас есть класс BigInt с оператором преобразования в int и явным оператором преобразования в std::string по какой-либо причине. Вы сможете сказать int i = myBigInt;, но вам нужно будет явно выполнить приведение (предпочтительно, используя static_cast), чтобы сказать std::string s = myBigInt;.   -  person chris    schedule 30.08.2012
comment
Не может явным образом также относиться к присваиванию? (т.е. int x(5);)   -  person Eitan Myron    schedule 27.02.2014
comment
@chris Идея явного неявного преобразования абсурдна. Держитесь подальше от этого!   -  person curiousguy    schedule 19.06.2018
comment
@curiousguy, Явного неявного преобразования нет.   -  person chris    schedule 20.06.2018
comment
@chris Существует явное ключевое слово, которое можно использовать при объявлении неявного преобразования.   -  person curiousguy    schedule 20.06.2018
comment
@curiousguy, это по своей сути неявное преобразование. Помещение explicit там объявляет явное преобразование в тип. Никакой скрытности в процессе.   -  person chris    schedule 20.06.2018
comment
@chris Явное преобразование - это плохо определенная концепция.   -  person curiousguy    schedule 20.06.2018
comment
@curiousguy: Что ты имеешь в виду? Все преобразования должны быть неявными? Выпустить всевозможные молчаливые забавные ошибки из-за случайных двусмысленностей? (См., Например, раздел «Проблема с безопасным логическим значением» на этой справочной странице C ++ или open-std.org/jtc1 /sc22/wg21/docs/papers/2007/n2333.html для (гораздо) более подробной информации о том, почему явное преобразование - это плохо определенная концепция. Это непродуманное утверждение.)   -  person Sz.    schedule 03.08.2019
comment
@Sz. Я имею в виду, что явное преобразование - это не вещь; это мусор. Кроме того, safe bool является смехотворным доказательством полезности идеи явного оператора, поскольку это даже не приложение этой идеи, а другой набор правил, что означает, что единственным практическим использованием явного оператора в SL является ad hoc и неприменимо к UDT.   -  person curiousguy    schedule 03.08.2019
comment
@chris, ты имел ввиду это: std::string s = static_cast<std::string>(myBigInt)? Если возможно, не могли бы вы подробнее рассказать о своем первом комментарии? Заранее большое спасибо!   -  person Milan    schedule 26.01.2021


Ответы (10)


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

Вот пример класса с конструктором, который можно использовать для неявных преобразований:

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, а int. Однако существует конструктор для Foo, который принимает int, поэтому этот конструктор можно использовать для преобразования параметра в правильный тип.

Компилятору разрешено сделать это один раз для каждого параметра.

Добавление перед конструктором ключевого слова explicit не позволяет компилятору использовать этот конструктор для неявных преобразований. Добавление его в вышеуказанный класс вызовет ошибку компилятора при вызове функции DoBar (42). Теперь необходимо явно вызывать преобразование с помощью DoBar (Foo (42))

Причина, по которой вы можете захотеть это сделать, - избежать случайной конструкции, которая может скрыть ошибки.
Надуманный пример:

  • У вас есть класс MyString с конструктором, который создает строку заданного размера. У вас есть функция print(const MyString&) (а также перегрузка print (char *string)), и вы вызываете print(3) (когда вы на самом деле намеревались вызвать print("3")). Вы ожидаете, что он напечатает 3, но вместо этого он печатает пустую строку длиной 3.
person Community    schedule 23.09.2008
comment
Хорошая запись, вы можете упомянуть, что несколько аргументов ctors с параметрами по умолчанию также могут действовать как один аргумент ctor, например, Object (const char * name = NULL, int otype = 0). - person maccullt; 24.09.2008
comment
Я думаю, также следует упомянуть, что следует подумать о том, чтобы сделать конструкторы с одним аргументом явными изначально (более или менее автоматически) и удалить явное ключевое слово только тогда, когда неявное преобразование требуется по дизайну. Я думаю, что конструкторы по умолчанию должны быть явными с ключевым словом implicit, чтобы они могли работать как неявные преобразования. Но это не так. - person Michael Burr; 26.08.2009
comment
@thecoshman: вы не объявляете параметр explicit - вы объявляете конструктор explicit. Но да: ваши параметры типа Foo должны быть сконструированы explicitely, они не будут сконструированы молча, просто подключив их параметры конструктора к функции. - person Christian Severin; 17.06.2011
comment
Просто к сведению, что при вызове print (3) в вашем примере функция должна быть print (const MyString &). Константа здесь обязательна, потому что 3 преобразуется во временный объект MyString, и вы не можете привязать временный объект к ссылке, если он не является const (еще один в длинном списке ошибок C ++) - person Larry; 10.07.2012
comment
Для полноты картины я добавляю, что в дополнение к преобразованию параметров ключевое слово explicit здесь также предотвратит использование формы назначения копии ctor (например, Foo myFoo = 42;) и потребует явных форм Foo myFoo = Foo (42); или Foo myFoo (42); - person Arbalest; 15.12.2012
comment
Но, если я правильно понимаю ваш последний контрпример, print("3") должен привести к ошибке компиляции, поскольку функция print фактически ожидает экземпляр MyString, а не фактическую строку, а конструктор класса MyString не позволяет компилятору автоматически приводить строка "3" к объекту MyString. Я неправильно это понимаю? - person mosegui; 18.03.2021
comment
@mosegui: да, вы правы, как указано в ответе. Я должен был сказать, что произошла перегрузка print, которая заняла char *. Я доработаю ответ. - person Skizz; 20.03.2021
comment
Это очень полезно, эти неявные преобразования также могут привести к неправильной практике. - person Maf; 29.03.2021

Допустим, у вас есть класс 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
};
person Eddie    schedule 23.09.2008
comment
И стоит отметить, что новые обобщенные правила инициализации C ++ 0x сделают String s = {0}; некорректным, вместо того, чтобы пытаться вызвать другой конструктор с нулевым указателем, как это сделал бы String s = 0;. - person Johannes Schaub - litb; 13.12.2010
comment
Несмотря на то, что это старый вопрос, кажется, стоит указать на несколько моментов (или попросить кого-нибудь меня поправить). Сделав форму int или оба ctors "явными", вы все равно будете иметь ту же ошибку, если бы вы использовали String mystring('x'), когда имели в виду String mystring("x"), не так ли? Кроме того, из комментария выше я вижу улучшенное поведение String s = {0} по сравнению с String s = 0 благодаря тому, что форма int для ctor стала «явной». Но, помимо знания приоритета ctors, как узнать намерение (то есть, как обнаружить ошибку) этого String s{0}? - person Arbalest; 05.08.2013
comment
Почему String mystring = 'x'; преобразуется в int? - person InQusitive; 22.03.2015
comment
@InQusitive: 'x' обрабатывается как целое число, поскольку char тип данных является просто 1-байтовым целым числом. - person DavidRR; 30.04.2015
comment
Проблема с вашим примером заключается в том, что он работает только с инициализацией копии (с использованием =), но не с прямой инициализацией (без использования =): компилятор все равно будет вызывать конструктор String(int) без генерации ошибки, если вы напишете String mystring('x');, как указал @Arbalest. Ключевое слово explicit предназначено для предотвращения неявных преобразований, которые происходят при прямой инициализации и разрешении функций. Лучшим решением для вашего примера была бы простая перегрузка конструктора: String(char c);. - person Maggyero; 11.08.2015

В C ++ конструктор только с одним обязательным параметром считается функцией неявного преобразования. Он преобразует тип параметра в тип класса. Хорошо это или нет, зависит от семантики конструктора.

Например, если у вас есть строковый класс с конструктором String(const char* s), это, вероятно, именно то, что вам нужно. Вы можете передать const char* функции, ожидающей String, и компилятор автоматически создаст для вас временный String объект.

С другой стороны, если у вас есть класс буфера, конструктор Buffer(int size) которого принимает размер буфера в байтах, вы, вероятно, не хотите, чтобы компилятор незаметно превращал ints в Buffers. Чтобы предотвратить это, вы объявляете конструктор с ключевым словом explicit:

class Buffer { explicit Buffer(int size); ... }

Сюда,

void useBuffer(Buffer& buf);
useBuffer(4);

становится ошибкой времени компиляции. Если вы хотите передать временный объект Buffer, вы должны сделать это явно:

useBuffer(Buffer(4));

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

person cjm    schedule 23.09.2008
comment
useBuffer ожидает lvalue для своего аргумента, useBuffer(Buffer(4)) тоже не будет работать из-за этого. Если поменять его на const Buffer&, Buffer&& или просто Buffer, он заработает. - person pqnet; 26.06.2017

Ключевое слово explicit сопровождает либо

  • конструктор класса X, который нельзя использовать для неявного преобразования первого (единственного) параметра в тип X

C ++ [class.conv.ctor]

1) Конструктор, объявленный без явного спецификатора функции, определяет преобразование типов его параметров в тип своего класса. Такой конструктор называется конструктором преобразования.

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

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

C ++ [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? (Где второй - то, что происходит.)

Использование ключевого слова explicit в интерфейсе класса заставляет пользователя интерфейса явно указывать желаемое преобразование.

Как говорит Бьярн Страуструп (в "The C ++ Programming Language", 4th Ed., 35.2.1, pp. 1011) на вопрос, почему std::duration не может быть неявно сконструирован из простого числа:

Если вы понимаете, что имеете в виду, скажите об этом прямо.

person Pixelchemist    schedule 10.07.2015

Этот ответ касается создания объекта с / без явного конструктора, поскольку он не рассматривается в других ответах.

Рассмотрим следующий класс без явного конструктора:

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;.

Здесь хорошая статья о явном конструкторы.

person Gautam    schedule 08.10.2013

Ключевое слово explicit превращает конструктор преобразования в конструктор без преобразования. В результате код менее подвержен ошибкам.

person SankararaoMajji    schedule 21.11.2012
comment
прямой ответ, если вы знаете С ++, если нет, вы можете перейти к ответу cjm ... - person n611x007; 25.01.2017

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

class C{
public:
    explicit C(void) = default;
};

int main(void){
    C c();
    return 0;
}

explicit-ключевое слово перед конструктором C(void) сообщает компилятору, что разрешен только явный вызов этого конструктора.

explicit-ключевое слово также может использоваться в операторах приведения типов, определяемых пользователем:

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 может помочь программисту избежать неявного, непреднамеренного приведения типов. Это использование стандартизировано в C ++ 11.

person Helixirr    schedule 14.05.2013
comment
C c(); в первом примере не означает то, что вы думаете: это объявление функции с именем c, которая не принимает параметров и возвращает экземпляр C. - person 6502; 20.02.2014
comment
explicit operator bool() также является версией safe bool для C ++ 11 и может использоваться неявно при проверках условий (и только при проверках условий, насколько мне известно). Во втором примере эта строка также действительна в main(): if (c) { std::cout << "'c' is valid." << std:: endl; }. Однако, кроме этого, его нельзя использовать без явного приведения типов. - person Justin Time - Reinstate Monica; 10.02.2016
comment
конструктор для явного вызова нет - person curiousguy; 13.06.2018
comment
@JustinTime Это глупая, сломанная версия безопасного bool. Сама идея явного неявного преобразования абсурдна. - person curiousguy; 13.06.2018
comment
@curiousguy Верно. Это немного похоже на кладж, нацеленный больше на то, чтобы его легко запомнить (вероятно, в надежде на то, что это будет часто использоваться), чем на следование английской логике, и спроектированный так, чтобы не быть полностью несовместимым с предыдущими безопасными реализациями bool (так что вы меньше может что-то сломать, если вы его замените). ИМО, по крайней мере. - person Justin Time - Reinstate Monica; 19.06.2018

Ссылка Cpp всегда полезна !!! Подробную информацию о явном спецификаторе можно найти здесь. Возможно, вам потребуется взглянуть на неявные преобразования и copy-initialization тоже.

Беглый взгляд

Явный спецификатор указывает, что конструктор или функция преобразования (начиная с C ++ 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
}
person selfboot    schedule 20.08.2016
comment
Да! Я думаю, что изучение примера и описания ключевого слова на cppreference - лучший способ понять explicit. - person dekuShrub; 18.04.2018
comment
explicit operator bool() vs. if - особый случай. Невозможно воспроизвести его с помощью определенных пользователем Bool, explicit operator Bool() и функции с именем If. - person curiousguy; 13.06.2018

Всегда рекомендуется создавать конструкторы с одним аргументом (включая те, которые имеют значения по умолчанию для _1 _, _ 2 _, ...), как уже было сказано. Как всегда с C ++: если вы этого не сделаете - вы захотите, чтобы вы сделали ...

Еще одна хорошая практика для классов - сделать создание копий и присваивание частными (или отключить их), если вам действительно не нужно это реализовывать. Это позволяет избежать возможных копий указателей при использовании методов, которые C ++ создаст для вас по умолчанию. Другой способ сделать это - использовать boost::noncopyable.

person fmuecke    schedule 01.10.2009
comment
Этот пост написан в 2009 году. Сегодня вы не объявляете их личными, а говорите = delete. - person v010dya; 02.10.2015

Конструкторы добавляют неявное преобразование. Чтобы подавить это неявное преобразование, необходимо объявить конструктор с явным параметром.

В C ++ 11 вы также можете указать «тип оператора ()» с таким ключевым словом http://en.cppreference.com/w/cpp/language/explicit С такой спецификацией вы можете использовать оператор в терминах явных преобразований и прямой инициализации объекта.

P.S. При использовании преобразований, определенных ПОЛЬЗОВАТЕЛЕМ (через конструкторы и оператор преобразования типов), разрешается использовать только один уровень неявных преобразований. Но вы можете комбинировать эти преобразования с преобразованиями других языков.

  • вверх по целому ряду (от char до int, float до удвоения);
  • стандартные преобразования (int в double);
  • преобразовать указатели объектов в базовый класс и в void *;
person bruziuz    schedule 23.01.2015