Что означает ключевое слово 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 *;