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


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

11 2422

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

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

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

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

в C++ [класс.конв.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 не может быть неявно построен из простого числа:

если вы знаете, что вы имеете в виду, быть явными об этом.

The explicit-ключевое слово может быть использовано для принудительного применения конструктора, который будет вызван явно.

class C{
public:
    explicit C(void) = default;
};

int main(void){
    C c();
    return 0;
}

the explicit - ключевое слово перед конструктором C(void) сообщает компилятору, что разрешен только явный вызов этого конструктора.

The 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.k.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*;