Pull to refresh

Миграция проекта на C++ с Visual Studio 2008 на Visual Studio 2010

Reading time4 min
Views1.3K
Не за горами принятие нового стандарта C++, и (к счастью?) разработчики компиляторов дают программистам возможность опробовать некоторые новые возможности, появляющиеся в языке, уже сейчас. В частности, последняя версия Visual Studio поддерживает ряд нововведений, о которых не раз и не два уже писалось. Но одно дело — демонстрировать возможности на синтетических примерах, другое — попробовать их в боевом режиме (очевидно, что на свой страх и риск). Эта статья — результат такого эксперимента. Итак.

Исходный материал: игровой проект с исходниками на 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).

Tags:
Hubs:
Total votes 18: ↑13 and ↓5+8
Comments2

Articles