Как избежать вызова конструктора копирования с итераторами вставки

template<typename OutputIterator>
void BlitSurface::ExtractFrames(OutputIterator it,
                                int frame_width, int frame_height,
                                int frames_per_row, int frames_per_column,
                                bool padding) const
{
    SDL_Surface ** temp_surf = SDL_Ex_ExtractFrames(_surface, frame_width, frame_height, frames_per_row, frames_per_column, padding);

    int surface_count = frames_per_row * frames_per_column;

    for(int i=0; i<surface_count; ++i)
    {
        BlitSurface bs;
        bs._surface = temp_surf[i];
        *it = bs;
        ++it;
    }

    delete [] temp_surf;
}

У меня есть эта функция, которая отлично работает. Единственная проблема заключается в том, что я не хочу вызывать конструктор копирования, потому что он копирует всю поверхность, и мне нужно только скопировать указатель. Я просто хочу использовать конструктор по умолчанию, а затем установить для элемента _surface значение temp_surface[i], например так:

for(int i=0; i<surface_count; ++i)
{
    it->_surface = temp_surf[i];
    ++it;
}

Это работает для обычных итераторов, но не для итераторов вставки. Как я могу исправить это, чтобы работать для обоих?

2 ответа

Решение

Действительно, вы хотите переместить InputIterator для использования с вставкой OutputIterator. Поскольку этого нет в C++03, должен существовать альтернативный способ указать, что желательно "мелкое" перемещение, а не "глубокое" копирование.

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

С моей головы это звучит как работа для пользовательского распределителя. Копии-конструкции распределителя по умолчанию с использованием размещения new; Вы можете определить альтернативный конструктор и вызвать его, используя вместо этого размещение new.

template< typename T >
struct move_traits {
    typedef T must_copy_type; // does not exist in specializations
};

template< typename T >
struct move_if_possible_allocator
    : std::allocator< T > {
    typedef move_traits<T> traits;

        // SFINAE selects this function if there is a specialization
    void construct( typename traits::may_move_type *obj, T &value ) {
        new( obj ) T(); // default construct
        traits::move_obj( *obj, value ); // custom routine
    }

        // SFINAE selects this function if traits is the base template
    void construct( typename traits::must_copy_type *obj, T const &value ) {
        new( obj ) T( value ); // copy construct (fallback case)
    }

    // define rebind... exercise for the reader ;v)
};

template<>
struct move_traits< BlitSurface > {
    typedef T may_move_type; // signal existence of specialization
    static void move_obj( BlitSurface &out, BlitSurface &in ) {
        // fill out and clear in
    }
}

Конечно, это прекрасно, чтобы добавить состояние в BlitSurface, чтобы отключить перемещение по move_obj, если некоторые объекты фактически скопированы в контейнер.

Упоминается, что вызывается конструктор копирования. В приведенном примере кажется, что контейнер, вероятно, определен для хранения BlitSurface. Что-то вроде std::vector. Это предположение с моей стороны из следующих строк:

    BlitSurface bs;
    bs._surface = temp_surf[i];
    *it = bs;

Насколько я понимаю, все контейнеры STD будут делать копии после вставки. Оттуда вы можете использовать объекты в контейнере по ссылке. Если вы не хотите, чтобы конструктор копирования вызывался в BlitSurface, я бы предложил, чтобы контейнер сохранял указатель на BlitSurface. Таким образом, когда контейнер делает свою копию при вставке объекта, он на самом деле делает копию - это указатель (а не объект BlitSurface, на который указывает).

    BlitSurface* bs = new BlitSurface;
    bs->_surface = temp_surf[i];
    *it = bs;

Имейте в виду, что этот подход выделяет кучу (то есть новую), поэтому память необходимо будет явно удалить позже, или в контейнере можно использовать интеллектуальный указатель определенного типа для обеспечения удаления (std::vector>).

Другие вопросы по тегам