Вопрос

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

Это было полезно?

Решение

Компилятору разрешено выполнить одно неявное преобразование для преобразования параметров в функцию.Это означает, что компилятор может использовать конструкторы, вызываемые с помощью один параметр для преобразования одного типа в другой, чтобы получить правильный тип параметра.

Вот пример класса с конструктором, который можно использовать для неявных преобразований:

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

С++ [класс.конв.ктор]

1) Конструктор, объявленный без явного спецификатора функции, определяет преобразование типов своих параметров в тип своего класса.Такой конструктор называется конструктором преобразования.

2) Явный конструктор конструирует объекты так же, как неявные конструкторы, но делает это только там, где явно используется синтаксис прямой инициализации (8.5) или где явно используются приведения типов (5.2.9, 5.4).Конструктор по умолчанию может быть явным конструктором;Такой конструктор будет использоваться для выполнения инициализации по умолчанию или значения инициализации (8.5).

  • или функция преобразования, которая рассматривается только для прямой инициализации и явного преобразования.

С++ [класс.конв.фкт]

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);

А 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 может помочь программисту избежать неявного, непреднамеренного приведения типов.Это использование было стандартизировано в С++11.

Справочник 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
}

Это уже обсуждалось(что такое явный конструктор).Но должен сказать, что здесь отсутствуют подробные описания.

Кроме того, всегда полезно создавать конструкторы с одним аргументом (в том числе со значениями по умолчанию для arg2, arg3,...), как уже говорилось.Как всегда с C++:если нет - ты пожалеешь об этом...

Еще одна хорошая практика для классов — сделать создание копирования и назначение закрытыми (т.отключите его), если только вам действительно не нужно его реализовать.Это позволяет избежать возможных копий указателей при использовании методов, которые C++ создаст для вас по умолчанию.Другой способ сделать это — получить из boost::noncopyable.

Конструкторы добавляют неявное преобразование.Чтобы подавить это неявное преобразование, необходимо объявить конструктор с явным параметром.

В С++ 11 вы также можете указать «тип оператора()» с помощью такого ключевого слова http://en.cppreference.com/w/cpp/language/explicit С такой спецификацией вы можете использовать оператор для явных преобразований и прямой инициализации объекта.

P.S.При использовании преобразований, определенных ПОЛЬЗОВАТЕЛЕМ (через конструкторы и оператор преобразования типов), допускается использование только одного уровня неявных преобразований.Но вы можете комбинировать эти преобразования с другими языковыми преобразованиями.

  • вверх по целым рядам (от char до int, от float до double);
  • стандартные преобразования (int в double);
  • конвертировать указатели объектов в базовый класс и в void*;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top