Что означает явное ключевое слово? - PullRequest
2617 голосов
/ 23 сентября 2008

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

Ответы [ 11 ]

3019 голосов
/ 23 сентября 2008

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

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

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.
1045 голосов
/ 23 сентября 2008

Предположим, у вас есть класс 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
};
145 голосов
/ 23 сентября 2008

В C ++ конструктор с одним обязательным параметром считается функцией неявного преобразования. Он преобразует тип параметра в тип класса. Хорошо это или нет, зависит от семантики конструктора.

Например, если у вас есть строковый класс с конструктором String(const char* s), это, вероятно, именно то, что вы хотите. Вы можете передать const char* функции, ожидающей String, и компилятор автоматически создаст для вас временный объект String.

С другой стороны, если у вас есть класс буфера, конструктор которого Buffer(int size) принимает размер буфера в байтах, вы, вероятно, не хотите, чтобы компилятор тихо превращал int s в Buffer s. Чтобы предотвратить это, вы объявляете конструктор с ключевым словом explicit:

class Buffer { explicit Buffer(int size); ... }

Таким образом,

void useBuffer(Buffer& buf);
useBuffer(4);

становится ошибкой во время компиляции. Если вы хотите передать временный объект Buffer, вы должны сделать это явно:

useBuffer(Buffer(4));

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

39 голосов
/ 08 октября 2013

Этот ответ о создании объекта с / без явного конструктора, поскольку он не рассматривается в других ответах.

Рассмотрим следующий класс без явного конструктора:

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;, вероятно, имело бы смысл.

Здесь - хорошее описание явных конструкторов.

37 голосов
/ 21 ноября 2012

Ключевое слово explicit создает конструктор преобразования в конструктор без преобразования. В результате код менее подвержен ошибкам.

36 голосов
/ 11 июля 2015

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

Если вы знаете, что имеете в виду, проясните это.

26 голосов
/ 20 декабря 2017

Конструкторы явного преобразования (только 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);
26 голосов
/ 14 мая 2013

Ключевое слово 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 .

18 голосов
/ 20 августа 2016

Cpp Ссылка всегда полезна !!! Подробности о явном спецификаторе можно найти здесь . Возможно, вам придется посмотреть неявные преобразования и copy-initialization тоже.

Быстрый просмотр

Явный спецификатор указывает, что конструктор или функция преобразования (начиная с 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
}
18 голосов
/ 02 октября 2009

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

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

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

...