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

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

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на Buffers. Чтобы предотвратить это, вы объявляете конструктор с помощью 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*;