Конструктор как конвертер
Набор конструкторов класса, принимающих единственный параметр, например, SmallInt(int) класса SmallInt, определяет множество неявных преобразований в значения типа SmallInt. Так, конструктор SmallInt(int) преобразует значения типа int в значения типа SmallInt.
extern void calc( SmallInt );
int i;
// необходимо преобразовать i в значение типа SmallInt
// это достигается применением SmallInt(int)
calc( i );
При вызове calc(i) число i преобразуется в значение типа SmallInt с помощью конструктора SmallInt(int), вызванного компилятором для создания временного объекта нужного типа. Затем копия этого объекта передается в calc(), как если бы вызов функции был записан в форме:
// Псевдокод на C++
// создается временный объект типа SmallInt
{
SmallInt temp = SmallInt( i );
calc( temp );
}
Фигурные скобки в этом примере обозначают время жизни данного объекта: он уничтожается при выходе из функции.
Типом параметра конструктора может быть тип некоторого класса:
class Number {
public:
// создание значения типа Number из значения типа SmallInt
Number( const SmallInt & );
// ...
};
В таком случае значение типа SmallInt можно использовать всюду, где допустимо значение типа Number:
extern void func( Number );
SmallInt si(87);
int main()
{ // вызывается Number( const SmallInt & )
func( si );
// ...
}
Если конструктор используется для выполнения неявного преобразования, то должен ли тип его параметра точно соответствовать типу подлежащего преобразованию значения? Например, будет ли в следующем коде вызван SmallInt(int), определенный в классе SmallInt, для приведения dobj к типу SmallInt?
extern void calc( SmallInt );
double dobj;
// вызывается ли SmallInt(int)? Да
// dobj преобразуется приводится от double к int
// стандартным преобразованием
calc( dobj );
Если необходимо, к фактическому аргументу применяется последовательность стандартных преобразований до того, как вызвать конструктор, выполняющий определенное пользователем преобразование. При обращении к функции calc()употребляется стандартное преобразование dobj из типа double в тип int. Затем уже для приведения результата к типу SmallInt вызывается SmallInt(int).
Компилятор неявно использует конструктор с единственным параметром для преобразования его типа в тип класса, к которому принадлежит конструктор. Однако иногда удобнее, чтобы конструктор Number(const SmallInt&) можно было вызывать только для инициализации объекта типа Number значением типа SmallInt, но ни в коем случае не для выполнения неявных преобразований. Чтобы избежать такого употребления конструктора, объявим его явным (explicit):
class Number {
public:
// никогда не использовать для неявных преобразований
explicit Number( const SmallInt & );
// ...
};
Компилятор никогда не применяет явные конструкторы для выполнения неявных преобразований типов:
extern void func( Number );
SmallInt si(87);
int main()
{ // ошибка: не существует неявного преобразования из SmallInt в Number
func( si );
// ...
}
Однако такой конструктор все же можно использовать для преобразования типов, если оно запрошено явно в форме оператора приведения типа:
SmallInt si(87);
int main()
{ // ошибка: не существует неявного преобразования из SmallInt в Number
func( si );
func( Number( si ) ); // правильно: приведение типа
func( static_cast< Number >( si ) ); // правильно: приведение типа
}