Комментарии

Галерея

Опрос

Из каких стран идет больше всего спама, попыток взлома на ваши серверы?:

Реализация внутреннего перемещения данных на примере std::vector и QVector с применением нового стандарта C++11

Новый стандарт C++ предлагает много улучшений и дополнений. Нововведения коснулись прежде всего ядра, а так же стандартной библиотеки STL. Есть и такие усовершенствования, при работе с которыми требуется оперировать совершенно новыми понятиями и заставлять себя мыслить иначе, мыслить в духе новых возможностей. Цель данной статьи рассмотреть некоторые из них.

Стоит отметить, что в то время как новый стандарт задействован в прикладном коде таких библиотек, как STL и Boost, то в Qt (на момент написания статьи только выпустили Qt 5.0) пока нет полноценной поддержки 11-х плюсов.

Здесь мы рассмотрим насколько полезными могут оказаться на практике rvalue-ссылки (предполагается, что читатель более-менее знаком с ними), и будет показано, как можно обойтись без них в определенных случаях при использовании QVector при отсутствии должной поддержки нового стандарта в Qt, получив при этом почти сходный результат.

Компилятор, который я использовал при написании и тестировании - GCC 4.7.2. Если вы используете Qt Cteator, не забудьте вставить строку CONFIG += c++11 в ваш .pro-файл. Буду благодарен тем, кто проделает данную работу с Clang и предоставит полученные результаты.

Итак, следующий класс продемонстрирует нам, что будет происходить с объектом при вставке/хранении его векторах:

#include <QVector>
#include <QTextStream>
#include <iostream>
 
struct MoveTest
{
    typedef MoveTest This;
 
    int i; // это поле не будет использоваться
 
    MoveTest    ()                  {}
    MoveTest    (const This&)       {std::cout << "constr copy\n";}
    MoveTest    (This&&) noexcept   {std::cout << "constr move\n";}
    ~MoveTest   () noexcept         {}
 
    This&   operator=   (const This&)       {std::cout << "copy\n"; return *this;}
    This&   operator=   (This&&) noexcept   {std::cout << "move\n"; return *this;}
 
    static void test()
    {
        std::cout << "std::move:\n";
        This t1;
        This t2(std::move(t1));
        This t3(std::move_if_noexcept(t2));
        t2 = std::move(t3);
        t1 = std::move_if_noexcept(t2);
        std::cout << "\n";
 
        std::cout << "QVector:\n";
        QVector<This> qmTest(5);
        qmTest.insert(qmTest.begin(), t1);
        std::cout << "\n";
 
        std::cout << "std::vector:\n";
        std::vector<This> mTest(5);
        mTest.insert(mTest.begin(), t1);
    }
};
 
int main(int argc, char *argv[])
{
    MoveTest::test();
}

Теперь давайте, запустим этот кусок на выполнение, потом выясним причинно-следственные связи:

std::move:
constr move
constr move
move
move
 
QVector:
constr copy
constr copy
constr copy
constr copy
constr copy
constr copy
copy
copy
copy
copy
copy
copy
 
std::vector:
constr copy
constr move
constr move
constr move
constr move
constr move

Как видно std::vector успешно перемещает 5 объектов типа MoveTest внутри себя. Это происходит после вставки объекта t1 (constr copy). В то же время QVector делает много лишних действий. Обратите внимание, как влияет спецификатор noexcept на вызов конструктора и оператора копирования. Если его убрать, то перемещение не будет работать внутри std::vector.
Для Qt, однако, не все так печально. Вызов конструктора копирования можно обойти использовав следующий макрос:

Q_DECLARE_TYPEINFO(MoveTest, Q_MOVABLE_TYPE);

Получим следующий вывод для QVector'а:

QVector:
constr copy
constr copy

Копирование происходит при вставке 2 раза, не зависимо от того, сколько элементов содержит QVector. Это достигается путем использования системной функции memmove. Но прежде чем использовать такой подход стоит учесть что не все объекты можно перемещать в памяти. Убедитесь, что внутри таких объектов не содержатся указатели на собственные поля. В этом случае будут возникать ошибки выполнения (сегфолты).

Если вы хотите сделать класс MoveTest шаблонным, то Q_DECLARE_TYPEINFO не сработает. Для шаблонов нужно использовать следующий макрос:

#define Q_DECLARE_MOVABLE_CONTAINER(CONTAINER) \
template <typename T> class CONTAINER; \
template <typename T> \
class QTypeInfo< CONTAINER<T> > \
{ \
public: \
    enum { \
        isPointer = false, \
        isComplex = true, \
        isStatic = false, \
        isLarge = (sizeof(CONTAINER<T>) > sizeof(void*)), \
        isDummy = false, \
        sizeOf = sizeof(CONTAINER<T>) \
    }; \
};
 
Q_DECLARE_MOVABLE_CONTAINER(MoveTest)

Этот макрос содержится в Qt, но по какой-то причине разработчики не предоставляют его рядовым программистам.

Замечание. Чтобы задействовать rvalue-ссылки, вы должны использовать std::move*. Для Qt справедливо было бы иметь что-то типа qMove. Глобально этот макрос не объявлен, но в коде Qt он все же имеется. Вы можете объявить его где-нибудь у себя в сторонке и использовать его втихаря.

Полезные ссылки:
http://www.rsdn.ru/article/submit/newcpp/newcpp.xml
http://ru.cppreference.com/w/cpp/language/noexcept
http://j.mp/cpp11ref
http://www.liveinternet.ru/users/3162595/post247699559/