Программисты видят в стандарте C++0x возможность использовать лямбда-функции и прочие малопонятные для меня сущности :). Я увидел в нем удобные средства, позволяющие исключить многие 64-битные ошибки.
Рассмотрим функцию, которая возвращает true, если хотя бы в одной из строк встречается последовательность "ABC".
typedef vector<string> ArrayOfStrings;
bool Find_Incorrect(const ArrayOfStrings &arrStr)
{
ArrayOfStrings::const_iterator it;
for (it = arrStr.begin(); it != arrStr.end(); ++it)
{
unsigned n = it->find("ABC");
if (n != string::npos)
return true;
}
return false;
};
Эта функция корректно ведет себя при компиляции Win32 версии и дает сбой при сборке в режиме Win64. Рассмотрим пример использования функции:
#ifdef IS_64
const char WinXX[] = "Win64";
#else
const char WinXX[] = "Win32";
#endif
int _tmain(int argc, _TCHAR* argv[])
{
ArrayOfStrings array;
array.push_back(string("123456"));
array.push_back(string("QWERTY"));
if (Find_Incorrect(array))
printf("Find_Incorrect (%s): ERROR!\n", WinXX);
else
printf("Find_Incorrect (%s): OK!\n", WinXX);
return 0;
}
Find_Incorrect (Win32): OK!
Find_Incorrect (Win64): ERROR!
Ошибка заключается в использовании типа unsigned для переменной "n", хотя функция find() возвращает значение типа string::size_type. В 32-битной программе тип string::size_type и unsigned совпадают, и мы получаем верный результат.
В 64-битной программе string::size_type и unsigned перестают совпадать. Так как подстрока не находится, функция find() возвращает значение string::npos, которое равно 0xFFFFFFFFFFFFFFFFui64. Это значение урезается до величины 0xFFFFFFFFu и помещается в 32-битную переменную. В результате условие 0xFFFFFFFFu == 0xFFFFFFFFFFFFFFFFui64 всегда false и мы получаем сообщение "Find_Incorrect (Win64): ERROR!".
Исправим код, используя тип string::size_type.
bool Find_Correct(const ArrayOfStrings &arrStr)
{
ArrayOfStrings::const_iterator it;
for (it = arrStr.begin(); it != arrStr.end(); ++it)
{
string::size_type n = it->find("ABC");
if (n != string::npos)
return true;
}
return false;
};
Теперь код работает корректно, хотя постоянно писать тип string::size_type длинно и не красиво. Можно переопределить его через typedef, но все равно это выглядит как-то сложно. Используя C++0x, мы можем сделать код гораздо изящней надежней.
Для этого мы воспользуемся ключевым словом auto. Ранее auto означало, что переменная создается на стеке, и подразумевалось неявно в случае, если вы не указали что-либо другое, например register. Теперь тип переменной, объявленной как auto, определяется компилятором самостоятельно на основе того, чем эта переменная инициализируется.
Следует заметить, что auto-переменная не сможет хранить значения разных типов в течение одного запуска программы. Си++ остается статически типизированным языком, и указание auto лишь говорит компилятору самостоятельно позаботиться об определении типа: после инициализации сменить тип переменной будет уже нельзя.
Воспользуемся ключевым словом auto в нашем коде. Проект был создан в Visual Studio 2005, а поддержка C++0x реализована только начиная с версии Visual Studio 2010. Поэтому для компиляции я воспользовался компилятором Intel C++, входящим в состав Intel Parallel Studio 11.1 и поддерживающим стандарт C++0x. Поддержка C++0x включается в разделе Language и носит название "Enable C++0x Support". Как видно из рисунка 1, эта опция является Intel Specific.
Рисунок 1 - Поддержка стандарта C++0x
Модифицированный код выглядит следующим образом:
bool Find_Cpp0X(const ArrayOfStrings &arrStr)
{
for (auto it = arrStr.begin(); it != arrStr.end(); ++it)
{
auto n = it->find("ABC");
if (n != string::npos)
return true;
}
return false;
};
Обратите внимание, как теперь объявлена переменная "n". Неправда ли, элегантно и устраняет от ряд ошибок, в том числе и 64-битных. Переменная "n" будет иметь ровно тот тип, который возвращает функция find(), то-есть string::size_type. Также обратите внимание, что исчезла строчка объявления итератора:
ArrayOfStrings::const_iterator it;
Объявлять переменную it внутри цикла было некрасиво (длинно). И поэтому объявление было вынесено отдельно. Теперь проблемы длины нет:
for (auto it = arrStr.begin(); ......)
Рассмотрим еще одно ключевое слово - decltype. Оно позволяет задать тип на основании типа другой переменной. Если бы в нашем коде, мы должны были объявить все переменные заранее, то можно было бы написать так:
bool Find_Cpp0X_2(const ArrayOfStrings &arrStr)
{
decltype(arrStr.begin()) it;
decltype(it->find("")) n;
for (it = arrStr.begin(); it != arrStr.end(); ++it)
{
n = it->find("ABC");
if (n != string::npos)
return true;
}
return false;
};
Конечно, в данном случае это бессмысленно, но может быть весьма удобно во многих ситуациях.
К сожалению (или к счастью для нас :-), хотя новый стандарт облегчает написание безопасного 64-битного кода, он не помогает в устранении уже существующих дефектов. Для того чтобы использовать memsize-тип или auto для устранений ошибки, эту ошибку нужно в начале обнаружить. Следовательно, актуальность использования инструмента Viva64 с приходом стандарта C++0x не изменится.
P.S.
Проект с кодом можно скачать здесь.