15.9.2. Конструктор като конвертор
Набор от конструктори на класове, които приемат единичен параметър, като SmallInt(int) на класа SmallInt, дефинира набор от неявни преобразувания към стойности на SmallInt. По този начин конструкторът SmallInt(int) преобразува стойности от тип
extern void calc( SmallInt); int i;
// трябва да конвертира i в стойност SmallInt
// това се постига чрез използване на SmallInt(int)
int към SmallInt стойности.
Когато се извика calc(i), i се преобразува в стойност SmallInt с помощта на конструктора SmallInt(int), извикан от компилатора за създаване на временен обект от желания тип. След това копие на този обект се предава на calc(), сякаш извикването на функцията е написано във формата:
C++ за начинаещи
// създава временен обект от тип SmallInt
SmallInt temp = SmallInt(i); calc(temp);
Къдравите скоби в този пример означават живота на този обект: той се унищожава, когато функцията излезе.
// създаване на стойност от тип Number от стойност от тип SmallInt Number( const SmallInt & );
Типът на параметъра на конструктора може да бъде типът на някакъв клас:
В този случай стойност от тип SmallInt може да се използва навсякъде, където е позволено.
extern void func (число); SmallInt si(87);
стойност от тип Number:
Ако конструктор се използва за извършване на имплицитно преобразуване, трябва ли типът на неговия параметър да съвпада точно с типа на стойността, която трябва да се преобразува? Например, ще извика ли следният код SmallInt(int) , дефиниран в класа
extern void calc( SmallInt); двойно dobj;
// извиква ли се SmallInt(int)? да
// dobj се преобразува от double към int
SmallInt, за прехвърляне на dobj към SmallInt?
Ако е необходимо, дадействителният аргумент се подлага на поредица от стандартни преобразувания, преди да бъде извикан конструкторът, който изпълнява дефинираното от потребителя преобразуване. При извикване на функцията calc() се използва стандартното преобразуване на dobj от double в int. След това се извиква SmallInt(int) за прехвърляне на резултата към SmallInt.
C++ за начинаещи
Компилаторът имплицитно използва конструктор с един параметър, за да преобразува неговия тип в типа на класа, към който принадлежи конструкторът. Понякога обаче е по-удобно конструкторът Number(const SmallInt&) да може да бъде извикан само за инициализиране на обект от тип Number със стойност от тип SmallInt и никога за извършване на неявни преобразувания. За да избегнете такива
// никога не използвайте изрично число ( const SmallInt & );
използване на конструктора, декларирайте го изрично ( explicit ):
Компилаторът никога не използва явни конструктори за изпълнение на неявни.
extern void func (число); SmallInt si(87);
Въпреки това, такъв конструктор все още може да се използва за преобразуване на тип if
func(Число(si)); // правилно: cast
func(static_cast (si)); // правилно: cast
изисква се изрично под формата на cast оператор:
15.10. Избор на трансформация А
Дефинирано от потребителя преобразуване се изпълнява като конвертор или конструктор. Както вече беше споменато, след преобразуването, извършено от преобразувателя, е позволено да се използва стандартното преобразуване за преобразуване на върнатата стойност към целевия тип. Трансформацията, извършена от конструктора, може също да бъде предшествана от стандартно преобразуване за преобразуване на типовеаргумент към типа на формалния параметър на конструктора.
C++ за начинаещи
Поредица от дефинирани от потребителя преобразувания е комбинацията от дефинирано от потребителя и стандартно преобразуване, което е необходимо за прехвърляне на стойност към целевия тип. Такава последователност изглежда така:
Последователност от стандартни трансформации -> Дефинирано от потребителя преобразуване ->
Последователност от стандартни трансформации
където дефинирано от потребителя преобразуване се изпълнява от конвертор или конструктор.
Възможно е да има две различни поредици от дефинирани от потребителя преобразувания за трансформиране на изходната стойност в целевия тип и след това компилаторът трябва да избере най-добрата от тях. Да видим как се прави.
Позволено е да се дефинират много конвертори в един клас. Например, нашият клас Number има два: оператор int() и оператор float(), като и двата могат да преобразуват обект Number в плаваща стойност. Естествено, можете да използвате конвертора Token::operator float() за директна трансформация. Но Token::operator int() също работи, защото неговият резултат е от тип int и следователно може да бъде преобразуван в float с помощта на стандартното преобразуване. Двусмислена ли е трансформацията, ако има множество
float(); operatorint(); // .
такива последователности? Или един от тях е по-добър от останалите? floatff = брой; // кой конвертор? float()
В такива случаи изборът на най-добрата последователност от дефинирани от потребителя трансформации се основава на анализ на последователността от трансформации, която се прилага след конвертора. В предишния пример можете да използвате тези двапоследователности:
1. оператор float() -> точно съвпадение
2. оператор int() -> стандартно преобразуване
Както беше обсъдено в раздел 9.3, точното съвпадение е по-добро от стандартната трансформация. Следователно първата последователност е по-добра от втората, което означава, че преобразувателят е избран
Може да се случи два различни конструктора да са приложими за преобразуване на стойност в целевия тип. В този случай се анализира последователността от стандартни трансформации, предхождащи извикването на конструктора: