явный - конвертирующий конструктор c++




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

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


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

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


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


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

В C ++ 11 вы также можете указать «тип оператора ()» с таким ключевым словом here С такой спецификацией вы можете использовать оператор с точки зрения явных преобразований и прямая инициализация объекта.

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

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

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

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

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

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


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

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

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, объявленную в this ответе). В этом случае создается объект как add_x add30 = 30; вероятно, имеет смысл.

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


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 .


Явные конструкторы преобразования (только 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-constructor