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

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

Другие вопросы по тегам