Что означает ключевое слово explicit
в C ++?
Что означает явное ключевое слово?
Ответы (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.
explicit
- вы объявляете конструктор explicit
. Но да: ваши параметры типа Foo
должны быть сконструированы explicite
ly, они не будут сконструированы молча, просто подключив их параметры конструктора к функции.
- person Christian Severin; 17.06.2011
print("3")
должен привести к ошибке компиляции, поскольку функция print
фактически ожидает экземпляр MyString
, а не фактическую строку, а конструктор класса MyString
не позволяет компилятору автоматически приводить строка "3"
к объекту MyString
. Я неправильно это понимаю?
- person mosegui; 18.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
};
String s = {0};
некорректным, вместо того, чтобы пытаться вызвать другой конструктор с нулевым указателем, как это сделал бы String s = 0;
.
- person Johannes Schaub - litb; 13.12.2010
String mystring('x')
, когда имели в виду String mystring("x")
, не так ли? Кроме того, из комментария выше я вижу улучшенное поведение String s = {0}
по сравнению с String s = 0
благодаря тому, что форма int для ctor стала «явной». Но, помимо знания приоритета ctors, как узнать намерение (то есть, как обнаружить ошибку) этого String s{0}
?
- person Arbalest; 05.08.2013
'x'
обрабатывается как целое число, поскольку char
тип данных является просто 1-байтовым целым числом.
- person DavidRR; 30.04.2015
=
), но не с прямой инициализацией (без использования =
): компилятор все равно будет вызывать конструктор 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)
которого принимает размер буфера в байтах, вы, вероятно, не хотите, чтобы компилятор незаметно превращал int
s в Buffer
s. Чтобы предотвратить это, вы объявляете конструктор с ключевым словом explicit
:
class Buffer { explicit Buffer(int size); ... }
Сюда,
void useBuffer(Buffer& buf);
useBuffer(4);
становится ошибкой времени компиляции. Если вы хотите передать временный объект Buffer
, вы должны сделать это явно:
useBuffer(Buffer(4));
Таким образом, если ваш однопараметрический конструктор преобразует параметр в объект вашего класса, вы, вероятно, не захотите использовать ключевое слово explicit
. Но если у вас есть конструктор, который просто принимает один параметр, вы должны объявить его как explicit
, чтобы компилятор не удивил вас неожиданными преобразованиями.
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
не может быть неявно сконструирован из простого числа:
Если вы понимаете, что имеете в виду, скажите об этом прямо.
Этот ответ касается создания объекта с / без явного конструктора, поскольку он не рассматривается в других ответах.
Рассмотрим следующий класс без явного конструктора:
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;
.
Здесь хорошая статья о явном конструкторы.
Ключевое слово explicit
превращает конструктор преобразования в конструктор без преобразования. В результате код менее подвержен ошибкам.
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.
C c();
в первом примере не означает то, что вы думаете: это объявление функции с именем c
, которая не принимает параметров и возвращает экземпляр C
.
- person 6502; 20.02.2014
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
Ссылка 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
}
explicit
.
- person dekuShrub; 18.04.2018
explicit operator bool()
vs. if
- особый случай. Невозможно воспроизвести его с помощью определенных пользователем Bool
, explicit operator Bool()
и функции с именем If
.
- person curiousguy; 13.06.2018
Всегда рекомендуется создавать конструкторы с одним аргументом (включая те, которые имеют значения по умолчанию для _1 _, _ 2 _, ...), как уже было сказано. Как всегда с C ++: если вы этого не сделаете - вы захотите, чтобы вы сделали ...
Еще одна хорошая практика для классов - сделать создание копий и присваивание частными (или отключить их), если вам действительно не нужно это реализовывать. Это позволяет избежать возможных копий указателей при использовании методов, которые C ++ создаст для вас по умолчанию. Другой способ сделать это - использовать boost::noncopyable
.
= delete
.
- person v010dya; 02.10.2015
Конструкторы добавляют неявное преобразование. Чтобы подавить это неявное преобразование, необходимо объявить конструктор с явным параметром.
В C ++ 11 вы также можете указать «тип оператора ()» с таким ключевым словом http://en.cppreference.com/w/cpp/language/explicit С такой спецификацией вы можете использовать оператор в терминах явных преобразований и прямой инициализации объекта.
P.S. При использовании преобразований, определенных ПОЛЬЗОВАТЕЛЕМ (через конструкторы и оператор преобразования типов), разрешается использовать только один уровень неявных преобразований. Но вы можете комбинировать эти преобразования с преобразованиями других языков.
- вверх по целому ряду (от char до int, float до удвоения);
- стандартные преобразования (int в double);
- преобразовать указатели объектов в базовый класс и в void *;
explicit
можно применять не только к конструкторам. Теперь он применим и к операторам преобразования. Допустим, у вас есть классBigInt
с оператором преобразования вint
и явным оператором преобразования вstd::string
по какой-либо причине. Вы сможете сказатьint i = myBigInt;
, но вам нужно будет явно выполнить приведение (предпочтительно, используяstatic_cast
), чтобы сказатьstd::string s = myBigInt;
. - person chris   schedule 30.08.2012int x(5);
) - person Eitan Myron   schedule 27.02.2014explicit
там объявляет явное преобразование в тип. Никакой скрытности в процессе. - person chris   schedule 20.06.2018std::string s = static_cast<std::string>(myBigInt)
? Если возможно, не могли бы вы подробнее рассказать о своем первом комментарии? Заранее большое спасибо! - person Milan   schedule 26.01.2021bool
является особенным в этом отношении a >. Эти ответы и поиск явных операторов преобразования приведут вас к большему количеству рецензий об этой функции и будут лучше подходить, чем цепочка комментариев. - person chris   schedule 26.01.2021