С++ 03 перемещение вектора в член класса через конструктор (семантика перемещения)

У меня есть доступ только к C++03, и я часто хочу переместить вектор в функцию так, как это можно сделать в C++11. Вопрос, как это сделать, чтобы не слишком запутать пользователя кода. Итак, мой вопрос в том, как программисты делали это до C++11.

Я знаю, что этот вектор можно «переместить» с помощью функции подкачки. Итак, вот что я придумал:

class Foo
{
public:
    Foo(std::vector<int>& vec)
    {
        using std::swap;
        swap(vec, m_vec);   // "move" vec into member vector
    }

private:
    std::vector<int> m_vec;
};

// usage:
std::vector<int> v(100, 1337);
Foo foo(v);
// v.empty() == true

Проблема с этим подходом заключается в том, что пользователю не очевидно, что его вектор будет перемещен в класс Foo. Есть ли передовое решение для такого рода проблем? Заранее спасибо!


person rozina    schedule 26.08.2014    source источник
comment
Вы можете назвать свою функцию Steal_Vector вместо Foo.   -  person pqnet    schedule 26.08.2014
comment
@pqnet Вероятно, нет, так как это конструктор.   -  person Quentin    schedule 26.08.2014
comment
Я не знаю, было ли это сделано, но для этого потребуется подробная документация. Думаю, было бы понятнее, если бы была функция Foo::swap_buffer.   -  person eerorika    schedule 26.08.2014
comment
@pqnet Я тоже этому учил. Но много раз вы хотите передать вектор в конструкторе. И мне не нравится создавать конструктор по умолчанию, а затем вызывать функцию-член 'steal_vector()' :)   -  person rozina    schedule 26.08.2014
comment
Странно писать целую строку using std::swap; вместо std::swap(...) По крайней мере, я понимаю, почему люди пишут using namespace std; - потому что им лень каждый раз набирать std::. Но здесь он даже больше набирает первое место.   -  person Slava    schedule 26.08.2014
comment
@Слава В данном случае верно. Я просто привык писать так для других случаев, когда есть специализированный своп для класса, не входящего в пространство имен std. Если вы не слышали об идиоме подкачки, это прояснит ситуацию: заголовок stackoverflow.com/questions/3279543/   -  person rozina    schedule 26.08.2014
comment
@rozina «Ожидаемый способ сделать определяемый пользователем тип заменяемым — предоставить замену функции, не являющейся членом, в том же пространстве имен, что и тип: подробности см. в разделе «Заменяемый». см. здесь en.cppreference.com/w/cpp/algorithm/swap   -  person Slava    schedule 26.08.2014
comment
@Слава, я бы все равно использовал метод swap класса vector. Было бы это плохой идеей?   -  person pqnet    schedule 26.08.2014
comment
@Slava Да, но когда вы вызываете swap, вы пишете с помощью 'std:: swap' перед вызовом swap. И теперь я просто всегда так делаю, так как это никогда не ошибается :) Я не против, если я наберу несколько лишних символов.   -  person rozina    schedule 26.08.2014


Ответы (5)


Вы можете определить тип, обертывающий ссылку, и функцию для ее обертывания, чтобы дать что-то похожее на семантику перемещения в месте вызова. Что-то в духе

template <typename T> struct move_ref {
    explicit move_ref(T & ref) : ref(ref) {}
    T & ref;
};

template <typename T> move_ref<T> move(T & t) {return move_ref<T>(t);}

Foo(move_ref< std::vector<int> > vec)
{
    using std::swap;
    swap(vec.ref, m_vec);   // "move" vec into member vector
}

Foo foo(move(v));

Кроме того, в Boost есть библиотека, позволяющая перемещать семантику без С++11.

person Mike Seymour    schedule 26.08.2014
comment
Очень похоже на C++11, мне нравится! Есть ли какие-либо серьезные недостатки в использовании этого? Я спрашиваю, потому что я много раз искал это с помощью Google и никогда не получал хороших решений, подобных этому. Использовался ли этот подход до C++11? - person rozina; 26.08.2014
comment
@rozina Не называйте это move, назовите его please_move или как-то иначе, чем имя в C ++ 11. Кроме того, рассмотрите возможность хранения указателя вместо ссылки и подумайте о семантике присваивания. - person Yakk - Adam Nevraumont; 26.08.2014
comment
@Yakk: он называется иначе, чем std::move в С++ 11, если только вы по глупости не начнете загрязнять пространство имен, в котором оно находится; и нет необходимости в присваивании, поскольку оно предназначено только для использования в качестве временного аргумента функции. - person Mike Seymour; 26.08.2014
comment
@MikeSeymour кивает, так что заблокируйте назначение. - person Yakk - Adam Nevraumont; 26.08.2014
comment
@Yakk: он заблокирован из-за неназначаемого члена. (Хотя, даже если бы это было не так, не было бы никакого смысла прыгать через обручи, чтобы предотвратить непреднамеренное, но безвредное использование класса.) - person Mike Seymour; 26.08.2014
comment
Эта версия не работает с временными файлами, не так ли? Хотя глядя на std::move, он также не принимает ссылку на константу. - person rozina; 26.08.2014
comment
@rozina: Нет, эта версия не будет работать с временными файлами. (В С++ 11 вы бы не использовали move для временного объекта, так как это уже rvalue, но я не думаю, что вы можете подражать этому поведению.) - person Mike Seymour; 26.08.2014

Вы можете использовать некоторую оболочку с явным именем:

template <typename T>
class MyMove
{
public:
    explicit MyMove(T& t) : t(t) {}

    T& get() {return t;}
private:
    T& t;
};

template <typename T>
MyMove<T> myMove(T& t) { return MyMove<T>(t); }

А потом

class Foo
{
public:
    Foo(MyMove<std::vector<int>> vec)
    {
        using std::swap;
        swap(vec.get(), m_vec);   // "move" vec into member vector
    }

private:
    std::vector<int> m_vec;
};

использование:

std::vector<int> v(100, 1337);
Foo foo(myMove(v));
person Jarod42    schedule 26.08.2014
comment
Я бы пометил аргумент MyMove() как const и запустил там const_cast, чтобы он работал и с временными значениями. По названию понятно, что мы все равно собираемся изменить заданное значение. - person pqnet; 26.08.2014
comment
Не работает с временными: Foo foo(myMove(std::vector<int>(100, 1337))) - person Piotr Skotnicki; 26.08.2014
comment
@ПиотрС. Temporeries могут привязываться только к константным ссылкам, верно? В этом случае я сомневаюсь, что будет решение C++03, которое будет работать с этим :) - person rozina; 26.08.2014
comment
@rozina: он может содержать const T& t;, а затем T& get() {return const_cast<T&>(t);} - person Piotr Skotnicki; 26.08.2014
comment
@rozina, вы можете использовать параметр const & и const_cast, чтобы убрать константность, что, вероятно, и делает boost в своей реализации. - person pqnet; 26.08.2014
comment
Обратите внимание, что член get() должен быть помечен const, иначе вы не сможете использовать его из временной переменной. - person pqnet; 26.08.2014
comment
@ПиотрС. Правда хорошая идея. Похоже, что С++ 03 может иметь семантику перемещения. Что меня очень радует :) Завтра я протестирую это и решение Майка и посмотрю, какое из них мне подходит лучше всего, и приму ответ :) - person rozina; 26.08.2014
comment
@rozina: см. мой ответ ниже - person Piotr Skotnicki; 26.08.2014
comment
@pqnet вы можете использовать методы, отличные от const, из временных объектов, сколько хотите. Старый трюк с MyClass() = MyClass() позволяет даже создать не-const ссылку на временное без приведения;) - person Yakk - Adam Nevraumont; 26.08.2014
comment
Использование const_cast (как предложено) может привести к UB, если это делается на константном объекте (но позволяет использовать временную переменную). - person Jarod42; 26.08.2014

Конечно, в C++03 можно использовать семантику перемещения.

Используя Boost.Move:

#include <vector>
#include <utility>
#include <boost/move/move.hpp>

class Foo
{
public:
    Foo(BOOST_RV_REF(std::vector<int>) vec)
    {
        std::swap(vec, m_vec);   // "move" vec into member vector
    }

private:
    std::vector<int> m_vec;
};

int main()
{
    std::vector<int> v(100, 1337);
    Foo foo(boost::move(v));
}

Или вы можете написать его самостоятельно, который работает с l-значениями, временными объектами и где r_value_ref<T> оболочка может использоваться как T& или const T& благодаря operator T&():

#include <vector>
#include <utility>

template <typename T>
class r_value_ref
{
public:
    explicit r_value_ref(const T& t) : t(t) {}

    T& get() const
    {
        return const_cast<T&>(t);
    }

    operator T&() const
    {
        return const_cast<T&>(t);
    }

private:    
    const T& t;
};

template <typename T>
r_value_ref<T> my_move(const T& t)
{
    return r_value_ref<T>(t);
}

class Foo
{
public:
    Foo(r_value_ref<std::vector<int> > vec)
    {
        m_vec.swap(vec); // no .get() required !
        // or std::swap(vec.get(), m_vec);
    }

private:
    std::vector<int> m_vec;
};

int main()
{
    Foo foo_from_r_value(my_move(std::vector<int>(100, 1337)));

    std::vector<int> v2(100, 1337);
    Foo foo_from_l_value(my_move(v2));
}

Ссылка на демонстрацию

Однако это небезопасно, т.е. r_value_ref<int> i(123); i.get() = 456; успешно компилируется, но приводит к неопределенному поведению. Написанные вручную обертки r-value-reference требуют немного больше усилий, чтобы сделать их абсолютно безопасными.

person Piotr Skotnicki    schedule 26.08.2014
comment
Я не уверен, что назвал бы разумной любую среду, которая позволяет использовать библиотеки boost, не разрешая С++ 11. - person pqnet; 26.08.2014
comment
@pqnet: Почему бы и нет? В некоторых средах используются старые компиляторы (без поддержки C++11) по разным причинам; нет особой причины избегать Boost в такой ситуации, и есть веская причина использовать подобные библиотеки для обеспечения недостающих функций. - person Mike Seymour; 26.08.2014
comment
@pqnet, почему бы и нет? обычно используется boost, когда у вас нет С++ 1 - person Paul Evans; 26.08.2014
comment
@pqnet: Boost.Move использует реальные ссылки на r-значения при компиляции как C++11. - person Piotr Skotnicki; 26.08.2014
comment
Спасибо чувак. Я забыл упомянуть, что да, у меня тоже нет доступа к Boost. Встроенный проект .. :) Я думаю, что решение Майка Сеймура в основном такое же, как это, но без повышения? - person rozina; 26.08.2014
comment
@rozina да, ответ Майка или Джарода одинаков, я думаю - person pqnet; 26.08.2014
comment
@ПиотрС. Я бы добавил автоматическое преобразование из r_value_ref<T> в const T&, чтобы, если конструктор перемещения не был определен для Foo, он возвращался к конструктору копирования, как в С++ 11. - person pqnet; 26.08.2014
comment
Помните, что, поддерживая временные файлы, вы теряете const-правильность. Foo foo_from_const(my_move(const_v)) попытается изменить объект const без предупреждения во время компиляции. - person Mike Seymour; 26.08.2014
comment
@MikeSeymour: я не понимаю вашу точку зрения, std::move() тоже это допускает: std::vector<int>&& ref = std::move(const_v); - person Piotr Skotnicki; 26.08.2014
comment
@PiotrS.: Нет. std::move действует как static_cast, который не может удалить квалификаторы const или volatile. Ваше использование const_cast удалит эти квалификаторы, потеряв константную правильность. - person Mike Seymour; 26.08.2014
comment
@MikeSeymour: неа.. моя ошибка, это работает, конечно, по-другому: T&& может быть назначено const T& - person Piotr Skotnicki; 26.08.2014

Если я правильно понял, вы хотите «переместить» вектор в члене класса с помощью вызова конструктора.

В С++ 11 вам нужно было бы предоставить конструктор с аргументом std::vector<T>&& и вызвать

Foo my_foo(std::move(my_vector));

В C++03 вы можете добавить "именованный конструктор" или друга, который сделает эту работу за вас.

template <class T>
class Foo
{
public:

  static Foo<T> move_from_vector(std::vector<T> & vector)
  {
    Foo<T> new_foo;
    new_foo.m_vec.swap(vector);
    return new_foo;
  }
private:
  std::vector<T> m_vec;
};

Где вы могли бы использовать это так:

int main()
{
    std::vector<int> h(5);
    Foo<int> g(Foo<int>::move_from_vector(h));
}

Таким образом, становится ясно, что вектор перемещается, и синтаксис перемещения не сильно отличается от C++11 (хотя, конечно, он не является общим).

Обратите внимание, что это скопирует Foo, если оптимизация отключена.

person Pixelchemist    schedule 26.08.2014
comment
template<class T> в template <class T>, исправьте - person Piotr Skotnicki; 26.08.2014
comment
Разве это не просто скопирует вектор в конце? В чем разница между реализацией move_vector_to_foo как у вас и с { return vector; }? - person pqnet; 26.08.2014
comment
@pqnet Компилятор не должен копировать Foo<int> сюда (из-за оптимизации возвращаемого значения / исключения копирования). - person Pixelchemist; 26.08.2014
comment
@Pixelchemist, но это зависит от компилятора и не гарантируется языком. И этого не произойдет, если у вас есть собственный конструктор копирования - person pqnet; 26.08.2014
comment
@pqnet Любой приличный компилятор пропустит копию, если включена оптимизация. (И он будет игнорировать копию, даже если присутствует пользовательский конструктор копирования, поскольку стандарт явно разрешает пропускать побочные эффекты.) - person Pixelchemist; 26.08.2014
comment
Копирование ellision в наши дни почти гарантировано почти для каждого компилятора, даже в C++03. FWIW, C++17 также собирается кодифицировать его как часть официального стандарта. - person NHDaly; 28.06.2016

Способ С++ 03 заключается в использовании std::auto_ptr для выражения передачи права собственности на данные.

class Foo
{
public:
    explicit Foo(std::auto_ptr<std::vector<int> > vec)
       : m_vec(vec)
    {
    }

private:
    Foo(const Foo&);
    Foo& operator=(const Foo&);

    std::auto_ptr<std::vector<int> > m_vec;
};

// usage:
std::auto_ptr<std::vector<int> > v(new std::vector<int>(100, 1337));
Foo foo(v);

// v.get() == 0

std::auto_ptr в сигнатуре четко указывает, что данные передаются внутри функции, оставляя сайт вызова с пустым указателем из-за характера копирования auto_ptr.

Обратите внимание на необходимость либо запретить, либо явно определить правильное создание копирования и назначение такого класса, поскольку конструктор копирования по умолчанию и назначение auto_ptr обычно не являются правильными.

person Andrei    schedule 26.08.2014