Не за горами принятие нового стандарта C++, и (к счастью?) разработчики компиляторов дают программистам возможность опробовать некоторые новые возможности, появляющиеся в языке, уже сейчас. В частности, последняя версия Visual Studio поддерживает ряд нововведений, о которых не раз и не два уже писалось. Но одно дело — демонстрировать возможности на синтетических примерах, другое — попробовать их в боевом режиме (очевидно, что на свой страх и риск). Эта статья — результат такого эксперимента. Итак.
Исходный материал: игровой проект с исходниками на C++. Активно используется boost (1.40), пользовательский интерфейс на Qt (4.x).
Задача: перевести проект на Visual Studio 2010 для дальнейшей разработки с использованием этого компилятора и его новых возможностей (в перспективе возможен переход gcc 4.5 и старше).
В процессе переноса кода исправлялись не только ошибки компиляции, вылезшие при сборки новым компилятором, но и выполнялась косметическая переработка кода, т. е. замена ряда используемых конструкций из boost'а на аналогичные из STL/языка (более глубокая переработка кода будет делаться позже). Ниже описаны некоторые проблемы, с которыми пришлось столкнуться, и способы их решения. Да, после внесения правок и финальной перекомпиляции всё запустилось и заработало.
Проблема вылезла при сборке Qt (4.6.3) на коде, наподобие следующего:
Такой код прекрасно собирается под VS 2005/2008, но не проходит под VS 2010. Для корректной сборки необходимо NULL заменить на nullptr:
Проблема — наличие у std::pair шаблонного конструктора:
Которого не было в предыдущих версиях STL.
К сожалению, текущая реализация (и спецификация) std::bind во многом менее функциональна, своего аналога из boost. В частности:
1. Имеются проблемы с компиляцией такого рода кода:
При этом такой вариант:
или
успешно компилируется. Ряд экспериментов показал, что имеются проблемы в реализации std::bind при согласовании ссылочных и не-ссылочных типов в цепочке связывания.
2. Для биндеров не перегружены арифметические и логические операции, по этому «классический»:
не принимается.
3. Плейсхолдеры вынесены из глобального неймспейса в std::placeholders, что (для удобства использования) вынуждает вводить алиас вида:
Лямбда-функции (в силу свое синтаксиса) несколько более «многословны», нежели «старые-добрые» биндеры. Предыдущий пример с поиском в контейнере может быть переписан так:
либо (тоже самое, но в одну строчку)
для сравнения:
и какому из вариантов отдавать предпочтение – нужно решать в каждом конкретном случае отдельно.
Реализация BOOST_FOREACH достаточно тяжеловесна и громоздка. И (на мой взгляд) в большинстве случаев может быть облегчена как минимум двумя способами. Способ номер раз — это упрощённая реализация макроса BOOST_FOREACH. Например, таким образом:
Способ номер два – заменять на связку std::for_each + lambda:
Какой вариант предпочительнее — решать, опять же, надо по месту. У себя пока сделал реимплементацию макроса (чтобы кода меньше править). Также не надо забывать, что для доступа к локальным переменным из лямбда-функции требуется их перечисление в capture-list'е.
В силу требований нового стандарта хранить содержимое строки последовательно (21.4.1, пп 5), из подобных конструкций:
можно исключать промежуточный буфер в виде вектора:
— BOOST_AUTO имеет смысл заменять на auto;
— BOOST_TYPEOF на decltype;
— cont.begin()/cont.end() на более обобщённые std::begin(cont)/std::end(cont).
Исходный материал: игровой проект с исходниками на C++. Активно используется boost (1.40), пользовательский интерфейс на Qt (4.x).
Задача: перевести проект на Visual Studio 2010 для дальнейшей разработки с использованием этого компилятора и его новых возможностей (в перспективе возможен переход gcc 4.5 и старше).
В процессе переноса кода исправлялись не только ошибки компиляции, вылезшие при сборки новым компилятором, но и выполнялась косметическая переработка кода, т. е. замена ряда используемых конструкций из boost'а на аналогичные из STL/языка (более глубокая переработка кода будет делаться позже). Ниже описаны некоторые проблемы, с которыми пришлось столкнуться, и способы их решения. Да, после внесения правок и финальной перекомпиляции всё запустилось и заработало.
NULL vs nullptr
Проблема вылезла при сборке Qt (4.6.3) на коде, наподобие следующего:
std::pair<SomeStruct*, SomeStruct*> pair(NULL, NULL);
Такой код прекрасно собирается под VS 2005/2008, но не проходит под VS 2010. Для корректной сборки необходимо NULL заменить на nullptr:
std::pair<SomeStruct*, SomeStruct*> pair(nullptr, nullptr);
Проблема — наличие у std::pair шаблонного конструктора:
template<class _Other1,
class _Other2>
pair(_Other1&& _Val1, _Other2&& _Val2)
Которого не было в предыдущих версиях STL.
boost::bind vs std::bind
К сожалению, текущая реализация (и спецификация) std::bind во многом менее функциональна, своего аналога из boost. В частности:
1. Имеются проблемы с компиляцией такого рода кода:
struct SomeStruct
{
int m_dataMember;
int GetValue() const {return m_dataMember;}
};
void PrintValue(int val) {/* ... */}
std::vector<SomeStruct> vec;
std::for_each(vec.begin(), vec.end(), std::bind(PrintValue, std::bind(&SomeStruct::m_DataMember, std::placeholders::_1)));
При этом такой вариант:
std::transform(vec.begin(), vec.end(), std::ostream_iterator(std::cout, ' '), std::bind(&SomeStruct::m_DataMember,std::placeholders::_1));
или
std::for_each(vec.begin(), vec.end(), std::bind(PrintValue, std::bind(&SomeStruct::GetValue(), std::placeholders::_1)));
успешно компилируется. Ряд экспериментов показал, что имеются проблемы в реализации std::bind при согласовании ссылочных и не-ссылочных типов в цепочке связывания.
2. Для биндеров не перегружены арифметические и логические операции, по этому «классический»:
auto res = std::find_if(vec.begin(), vec.end(), std::bind(&SomeStruct::m_DataMember, std::placeholders::_1) == 1);
не принимается.
3. Плейсхолдеры вынесены из глобального неймспейса в std::placeholders, что (для удобства использования) вынуждает вводить алиас вида:
namespace ph = std::placeholders;
bind vs lambda
Лямбда-функции (в силу свое синтаксиса) несколько более «многословны», нежели «старые-добрые» биндеры. Предыдущий пример с поиском в контейнере может быть переписан так:
auto res = std::find_if(vec.begin(), vec.end(), [](SomeStruct const& s)
{
return s.m_Value == 1;
});
либо (тоже самое, но в одну строчку)
auto res = std::find_if(vec.begin(), vec.end(), [](SomeStruct const& s) {return s.m_DataMember == 1;});
для сравнения:
auto res = std::find_if(vec.begin(), vec.end(), boost::bind(&SomeStruct::m_DataMember, _1) == 1);
и какому из вариантов отдавать предпочтение – нужно решать в каждом конкретном случае отдельно.
Упрощение BOOST_FOREACH
Реализация BOOST_FOREACH достаточно тяжеловесна и громоздка. И (на мой взгляд) в большинстве случаев может быть облегчена как минимум двумя способами. Способ номер раз — это упрощённая реализация макроса BOOST_FOREACH. Например, таким образом:
#define FOREACH(Var, cont) \
if (bool do_continue_ = true) \
for (auto foreach_iter = std::begin(cont); foreach_iter != std::end(cont); ++ foreach_iter, do_continue_ = true) \
for (Var = *foreach_iter; do_continue_; do_continue_ = false)
Способ номер два – заменять на связку std::for_each + lambda:
std::for_each(std::begin(vec), std::end(vec), [](TestClass const& val)
{
std::cout << val.m_Value << ' ';
});
Какой вариант предпочительнее — решать, опять же, надо по месту. У себя пока сделал реимплементацию макроса (чтобы кода меньше править). Также не надо забывать, что для доступа к локальным переменным из лямбда-функции требуется их перечисление в capture-list'е.
vector<charT> vs basic_string<charT>
В силу требований нового стандарта хранить содержимое строки последовательно (21.4.1, пп 5), из подобных конструкций:
std::ifstream fs(/*...*/);
// ...
std::vector<char> buff(some_size);
fs.read(&buff[0], some_size);
return std::string(buff.begin(), buff.end());
можно исключать промежуточный буфер в виде вектора:
std::string ret_val(some_size, '\0');
fs.read(&ret_val[0], some_size);
return ret_val;
Мелочи
— BOOST_AUTO имеет смысл заменять на auto;
— BOOST_TYPEOF на decltype;
— cont.begin()/cont.end() на более обобщённые std::begin(cont)/std::end(cont).