Что означает явное ключевое слово?
Что это explicit
Ключевое слово означает в C++?
12 ответов
Компилятору разрешено делать одно неявное преобразование для разрешения параметров функции. Это означает, что компилятор может использовать конструкторы, вызываемые с одним параметром, для преобразования из одного типа в другой, чтобы получить правильный тип для параметра.
Вот пример класса с конструктором, который можно использовать для неявных преобразований:
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(int size)
класс с конструктором, который создает строку заданного размера. У вас есть функцияprint(const MyString&)
и ты звонишьprint(3)
(когда вы на самом деле намеревались позвонитьprint("3")
). Вы ожидаете, что он напечатает "3", но вместо этого он напечатает пустую строку длины 3.
Предположим, у вас есть класс 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
};
В C++ конструктор с одним обязательным параметром считается неявной функцией преобразования. Он преобразует тип параметра в тип класса. Хорошо это или нет, зависит от семантики конструктора.
Например, если у вас есть строковый класс с конструктором 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
чтобы компилятор не удивил вас неожиданными преобразованиями.
Ключевое слово 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
? (Где второй, что происходит.)
Использование явного ключевого слова в интерфейсе класса заставляет пользователя интерфейса быть явным о желаемом преобразовании.
Как говорит Бьярн Страуструп (в "Языке программирования C++", 4-е изд., 35.2.1, с. 1011) на вопрос, почему std::duration
не может быть неявно построен из простого числа:
Если вы знаете, что вы имеете в виду, проясните это.
Конструкторы явного преобразования (только C++)
Явный спецификатор функции контролирует нежелательные неявные преобразования типов. Он может использоваться только в объявлениях конструкторов внутри объявления класса. Например, за исключением конструктора по умолчанию, конструкторы в следующем классе являются конструкторами преобразования.
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);
Этот ответ о создании объекта с / без явного конструктора, так как он не рассматривается в других ответах.
Рассмотрим следующий класс без явного конструктора:
class Foo
{
public:
Foo(int x) : m_x(x)
{
}
private:
int m_x;
};
Объекты класса Foo могут быть созданы 2 способами:
Foo bar1(10);
Foo bar2 = 20;
В зависимости от реализации, второй способ создания экземпляра класса Foo может сбивать с толку, или не то, что задумал программист. Префикс explicit
Ключевое слово для конструктора будет генерировать ошибку компилятора в Foo bar2 = 20;
,
Обычно рекомендуется объявлять конструкторы с одним аргументом как explicit
Если ваша реализация специально не запрещает это.
Обратите внимание, что конструкторы с
- аргументы по умолчанию для всех параметров, или
- аргументы по умолчанию для второго параметра и далее
оба могут использоваться как конструкторы с одним аргументом. Так что вы можете сделать это также explicit
,
Пример, когда вы намеренно не хотите делать явным свой конструктор с одним аргументом, это если вы создаете функтор (посмотрите на структуру 'add_x', объявленную в этом ответе). В таком случае создание объекта как add_x add30 = 30;
вероятно, имеет смысл.
Вот хорошая рецензия на явные конструкторы.
explicit
Ключевое слово делает конструктор преобразования в конструктор без преобразования. В результате код менее подвержен ошибкам.
explicit
-keyword может использоваться для принудительного вызова конструктора в явном виде.
class C{
public:
explicit C(void) = default;
};
int main(void){
C c();
return 0;
}
explicit
ключевое слово перед конструктором 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 может помочь программисту избежать неявных, непреднамеренных приведений. Это использование было стандартизировано в C++ 11.
Cpp Reference всегда полезен!!! Подробности о явном спецификаторе можно найти здесь. Возможно, вам придется посмотреть на неявные преобразования и инициализацию копирования тоже.
Беглый взгляд
Явный спецификатор указывает, что конструктор или функция преобразования (начиная с 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
}
Это уже обсуждалось ( что такое явный конструктор). Но я должен сказать, что в нем отсутствуют подробные описания, найденные здесь.
Кроме того, всегда полезно создавать конструкторы с одним аргументом (в том числе со значениями по умолчанию для arg2,arg3,...), как уже было сказано. Как всегда с C++: если вы этого не сделаете - вы бы хотели, чтобы вы сделали...
Еще одна полезная практика для классов - делать конструкцию копирования и присваивание частными (иначе говоря, отключать ее), если вам действительно не нужно это реализовывать. Это позволяет избежать возможных копий указателей при использовании методов, которые C++ создаст для вас по умолчанию. Другой способ сделать это является производным от boost::noncopyable.
Конструкторы добавляют неявное преобразование. Чтобы подавить это неявное преобразование, необходимо объявить конструктор с явным параметром.
В C++11 вы также можете указать "оператор type()" с таким ключевым словом http://en.cppreference.com/w/cpp/language/explicit С такой спецификацией вы можете использовать оператор в терминах явных преобразований, и прямая инициализация объекта.
PS При использовании преобразований, определенных BY USER (через конструкторы и оператор преобразования типов), допускается использование только одного уровня неявных преобразований. Но вы можете объединить эти преобразования с другими языковыми преобразованиями.
- вверх по целым разрядам (char до int, float до double);
- стандартные преобразования (int в double);
- конвертировать указатели объектов в базовый класс и в void*;
В других ответах отсутствует один важный фактор, который я собираюсь упомянуть здесь.
Наряду с ключевым словом «delete», «explicit» позволяет вам контролировать способ, которым компилятор будет генерировать специальные функции-члены — конструктор по умолчанию, конструктор копирования, оператор присваивания копирования, деструктор, конструктор перемещения и присваивание перемещения.
См . https://docs.microsoft.com/en-us/cpp/cpp/explicitly-defaulted-and-deleted-functions