Что означает ключевое слово explicit?
11 ответов
компилятору разрешено выполнить одно неявное преобразование для разрешения параметров в функцию. Это означает, что компилятор может использовать конструкторы, вызываемые с помощью один параметр для преобразования из одного типа в другой для того, чтобы получить правильный тип параметра.
вот пример класса с конструктором, который можно использовать для неявных преобразований:
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);
}
аргумент не
Предположим, у вас есть класс 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 с конструктором 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
чтобы компилятор не удивил вас неожиданными преобразованиями.
этот ответ касается создания объекта с / без явного конструктора, поскольку он не охватывается другими ответами.
рассмотрим следующий класс без явного конструктора:
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
сопровождает либо
- конструктор класса X, который нельзя использовать для неявного преобразования первого (только любого) параметра в тип X
в C++ [класс.conv.ctor]
1) конструктор, объявленный без функции-спецификатора explicit, указывает преобразование из типов его параметров в тип его класса. Такой конструктор называется конструктор преобразования.
2) явный конструктор создает объекты так же, как и неявные конструкторы, но делает это только там, где явно используется синтаксис прямой инициализации (8.5) или где приведения (5.2.9, 5.4). Конструктор по умолчанию может быть явным конструктором; такой конструктор будет использоваться для выполнения инициализации по умолчанию или valueinitialization (8.5).
- или функция преобразования, которая учитывается только для прямая инициализация и явное преобразование.
в C++ [класс.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 в интерфейсе класса обеспечивает пользователь интерфейса должен быть явным о желаемом преобразовании.
как выразился Бьярне Страуструп (в "языке программирования C++", 4-е изд., 35.2.1, стр. 1011) на вопрос, почему std::duration
не может быть неявно построен из простого числа:
если вы знаете, что вы имеете в виду, быть явными об этом.
на 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
- ключевое слово применяет только явные приведения, чтобы быть действительным, поэтому bool b = c;
будет недействительным приведением этот случай. В подобных ситуациях explicit
-ключевое слово может помочь программисту избежать неявных, непреднамеренных бросков. Это использование было стандартизировано в C++11.
Это уже обсуждалось (что такое явный конструктор). Но я должен сказать, что ему не хватает подробных описаний здесь.
кроме того,это всегда хорошая практика кодирования, чтобы сделать ваши конструкторы одного аргумента (в том числе со значениями по умолчанию для arg2, arg3,...) как уже говорилось. Как всегда с C++: если вы этого не сделаете - вы пожалеете об этом...
еще одна хорошая практика для классов - сделать создание копий и назначение частными (a.к. a. отключите его), если вам действительно не нужно его реализовать. Это позволяет избежать возможных копий указателей при использовании методов, которые C++ создаст для вас по умолчанию. Другой способ сделать это-получить от boost::noncopyable.
ссылка Cpp всегда полезна!!! Подробности о явном спецификаторе можно найти здесь. Вы можете посмотреть неявные преобразования и копирования-инициализации тоже.
быстрый просмотр
явный спецификатор указывает, что конструктор или функция преобразования (начиная с 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
}
явные конструкторы преобразования (только 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);
конструкторы добавляют неявное преобразование. Чтобы подавить это неявное преобразование, необходимо объявить конструктор с параметром explicit.
В C++11 вы также можете указать "тип оператора ()" с таким ключевым словом http://en.cppreference.com/w/cpp/language/explicit с такой спецификацией вы можете использовать оператор с точки зрения явных преобразований и прямой инициализации объекта.
P. S. Когда с помощью преобразований, определенных пользователем (через конструкторы и оператор преобразования типов) допускается только один уровень неявных преобразований. Но вы можете объединить эти преобразования с другими языковыми преобразованиями
- вверх по интегральным рангам (char до int, float до double);
- стандартные преобразования (int в double);
- преобразование указателей объектов в базовый класс и void*;