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

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

Ответы

Ответ 1

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

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

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, а <<26 > . Однако существует конструктор для Foo, который принимает int, поэтому этот конструктор может быть использован для преобразования параметра в правильный тип.

Компилятору разрешено делать это один раз для каждого параметра.

Префикс ключевого слова explicit конструктору запрещает компилятору использовать этот конструктор для неявных преобразований. Добавление его в класс выше приведет к ошибке компилятора при вызове функции DoBar (42). Теперь необходимо явно вызвать преобразование с помощью DoBar (Foo (42))

Причина, по которой вы захотите сделать это, - это избежать случайного построения, которое может скрыть ошибки. Продуманный пример:

  • У вас есть класс MyString(int size) с конструктором, который строит строку заданного размера. У вас есть функция print(const MyString&), и вы вызываете print(3) (когда вы на самом деле намеревались позвонить print("3")). Вы ожидаете, что он напечатает "3", но вместо этого печатает пустую строку длиной 3.

Ответ 2

Предположим, у вас есть класс 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
};

Ответ 3

В С++ конструктор с одним обязательным параметром считается неявной функцией преобразования. Он преобразует тип параметра в тип класса. Является ли это хорошей вещью или нет, зависит от семантики конструктора.

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

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

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

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

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

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

useBuffer(Buffer(4));

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

Ответ 4

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

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

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

Вот хорошая запись о явных конструкторах.

Ответ 5

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

Ответ 6

Ключевое слово explicit сопровождает либо

  • конструктор класса X, который нельзя использовать для неявного преобразования первого (любого только) параметра в тип X

С++ [class.conv.ctor]

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

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

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

С++ [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? (Где вторая происходит, что происходит.)

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

Как пишет Бьярне Страуструп (в "Язык программирования на С++", 4-е изд., 35.2.1, стр. 1011), вопрос о том, почему std::duration не может быть неявно построено из простого числа:

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

Ответ 7

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

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

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

explicit -keyword перед конструктором 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.

Ответ 8

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

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

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

Ответ 9

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

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

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

Ответ 10

Явные конструкторы преобразования (только для С++)

Явный спецификатор функций управляет нежелательным неявным типом преобразования. Его можно использовать только в объявлениях конструкторов в декларации класса. Например, кроме конструктора, конструкторы в следующем классе являются преобразованием Конструкторы.

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

Ответ 11

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

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

PS При использовании преобразований, определенных пользователем USER (через конструкторы и оператор преобразования типов), допускается использование только одного уровня неявных преобразований. Но вы можете комбинировать эти преобразования с другими языковыми преобразованиями

  • (char to int, float to double);
  • стандартные преобразования (int double);
  • конвертировать указатели объектов в базовый класс и void *;