Поделиться через


Строковый литерал

Обработка строковых литералов вVisual C++ 2010 изменилось по сравнению с управляемыми расширениями для C++.

В управляемых расширениях для конструкции языка C++ управляемый строковый литерал указывался с предшествующим элементом S. Пример.

String *ps1 = "hello";
String *ps2 = S"goodbye";

Служебные данные о производительности между двумя циклами инициализации представляют собой нестандартные сведения, про что свидетельствует презентация CIL в ildasm:

// String *ps1 = "hello";
ldsflda    valuetype $ArrayType$0xd61117dd
     modopt([Microsoft.VisualC]Microsoft.VisualC.IsConstModifier) 
     '?A0xbdde7aca.unnamed-global-0'

newobj instance void [mscorlib]System.String::.ctor(int8*)
stloc.0

// String *ps2 = S"goodbye";
ldstr      "goodbye"
stloc.0

Этот нестандартный процесс сохранения представлен, чтобы еще раз подчеркнуть необходимость использования элемента S перед строковым литералом. В новом синтаксисе обработка строковых литералов является прозрачной и определяется контекстом. Больше нет необходимости указывать S.

Случаи, в которых необходимо явно направлять компилятор к одной или другой интерпретации. В таких случаях используется явное приведение. Пример.

f( safe_cast<String^>("ABC") );

Более того, в результате простого, а не стандартного преобразования было установлено соответствие между строковым литералом и String. При этом здесь не указывается насколько изменилось разрешение наборов перегруженных функций, включающих String и const char* в качестве соревнующихся формальных параметров. Разрешение, когда-то назначенное экземпляру const char*, теперь отмечено как неоднозначное. Пример.

ref struct R {
   void f(const char*);
   void f(String^);
};

int main () {
   R r;
   // old syntax: f( const char* );
   // new syntax: error: ambiguous
   r.f("ABC"); 
}

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

  1. Сбор потенциальных функций. Потенциальные функции — это методы, которые лексически соответствуют имени вызванной функции в пределах области. Например, поскольку f() вызван с помощью экземпляра R, все функции, названные f и не относящиеся к элементам R (или его иерархии базового класса), не являются потенциальными функциями. В приведенном примере указаны две потенциальные функции. Это две потенциальные функции R с именем f. Если набор потенциальных функций пуст, на данном этапе возникнет ошибка.

  2. Набор переменных функций из числа потенциальных функций. Переменная функция — это функция, к которой можно обращаться с помощью аргументов, указанных в вызове (с указанием количества и типа аргументов). В приведенном примере обе потенциальные функции также являются переменными функциями. Если набор переменных функций пуст, на данном этапе возникнет ошибка.

  3. Выберите функцию, которая наиболее точно соответствует вызову. Данные процесс осуществляется путем ранжирования преобразований, используемых для трансформации аргументов в тип параметров переменной функции. Если доступен один параметр функции, процесс считается простым, с добавление параметров процесс усложняется. При отсутствии оптимального соответствия на данном этапе происходит ошибка вызова. То есть, если преобразования, требуемые для трансформации типа фактического аргумента в тип формального параметра, одинаково хорошо подходят. Вызов отмечается как неоднозначный.

В управляемых расширениях разрешение данного вызова обращается к экземпляру const char* в качестве оптимального соответствия. В новом синтаксисе преобразования, которые необходимы для согласования "abc" с const char* и String^, теперь эквивалентны (т. е., одинаково подходящие), поэтому вызов отмечен как несоответствующий (т. е. неоднозначный).

В связи с этим возникают два вопроса.

  • Каков тип фактического аргумента "abc"?

  • Какой алгоритм используется для определения, в каком случае больше подходит тот или иной тип преобразования?

Типом строкового литерала "abc" является const char[4]. Необходимо помнить, что в конце каждого строкового литерала ставится неявный символ, "null".

Алгоритм, использующийся для определения, в каких ситуациях подходят те или иные преобразования, размещает потенциальные типы преобразований в иерархию. Далее приводится пояснение к этой иерархии, учитывая, что все преобразования являются неявными. Явная запись приведения переопределяет иерархию, также как круглые скобки переопределяют обычный порядок применения операторов.

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

  2. Повышение лучше, чем стандартное преобразование. Например, повышение short int до int считается лучшим решением, по сравнению с преобразованием int в double.

  3. Стандартное преобразование лучше, чем упаковка-преобразование. Например, преобразование int в double, считается лучшим решением, чем преобразование упаковки int в Object.

  4. Упаковка-преобразование лучше, чем неявное определенное пользователем преобразование. Например, преобразование упаковки int в Object лучше, чем применение оператора преобразования класса значения SmallInt.

  5. Неявное определенное пользователем преобразование лучше, чем отсутствие преобразования. Неявное определенное пользователем преобразование является последним способом избежания ошибки (с механизмом, когда формальная подпись может содержать массив параметров или многоточие в этой позиции).

Что же означает фраза, "точное соответствие не обязательно представляет точное соответствие"? Например, const char[4] не является точным соответствием для const char* или String^, поэтому неопределенность в примере состоит в конфликте между двумя точными соответствиями!

Когда возникает точное соответствие, оно включает ряд стандартных преобразований. В ISO-C++ существует четыре стандартных преобразования, которые могут применяться и все же считаться точным соответствием. Три из них относятся к преобразованию L-значений. Четвертый тип считается квалификационным преобразованием. Три типа преобразования L-значений представляют лучшее точное соответствие, чем тип, требующий квалификационного преобразования.

Одной из форм преобразования L-значений является преобразование собственного массива в указатель. Компонентами соответствия являются const char[4] и const char*. Таким образом, соответствие f("abc") f(const char*) является точным соответствием. В ранних версиях языка такое соответствие считалось наиболее оптимальным.

Чтобы отметить компилятором все неопределенности, необходимо, чтобы преобразование const char[4] в String^ также считалось точным соответствием (и одновременно стандартным соответствием). Это изменение было представлено в новой версии языка. Поэтому вызов теперь помечен как неопределенный.

См. также

Ссылки

System::String Handling in Visual C++

Основные понятия

Общие изменения в языке