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

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

2387
задан Skizz 23 сент. '08 в 16:58
источник поделиться
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);
}

Аргумент не является объектом Foo, а <<26 > . Однако существует конструктор для Foo, который принимает int, поэтому этот конструктор может быть использован для преобразования параметра в правильный тип.

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

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

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

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

Предположим, у вас есть класс 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
};
971
ответ дан Eddie 23 сент. '08 в 17:09
источник поделиться

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

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

133
ответ дан cjm 23 сент. '08 в 19:37
источник поделиться

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

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

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

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

34
ответ дан Gautam 08 окт. '13 в 17:43
источник поделиться

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

32
ответ дан SankararaoMajji 21 нояб. '12 в 5:36
источник поделиться

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

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

31
ответ дан Pixelchemist 11 июля '15 в 2:48
источник поделиться

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.

25
ответ дан Helixirr 14 мая '13 в 12:28
источник поделиться

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

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

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

17
ответ дан fmuecke 02 окт. '09 в 1:00
источник поделиться

Ссылка 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
}
15
ответ дан selfboot 20 авг. '16 в 15:45
источник поделиться

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

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

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);
10
ответ дан coding_ninza 20 дек. '17 в 15:19
источник поделиться

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

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

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

  • (char to int, float to double);
  • стандартные преобразования (int double);
  • конвертировать указатели объектов в базовый класс и void *;
4
ответ дан bruziuz 23 янв. '15 в 12:26
источник поделиться

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