C++03 moving a vector into a class member through

2020-07-10 10:39发布

问题:

I only have access to C++03 and I often want to move a vector into a function the way you can do it in C++11. The question how to do it not to confuse the user of the code too much. So my question is how did programmers do it before C++11.

I know that vector can be "moved" using swap function. So here is what I have come up with:

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

The problem with this approach is that its not evident to the user that their vector will be moved into the class Foo. Is there a best practice solution to this kind of problem? Thanks in advance!

回答1:

You could define a type wrapping a reference, and a function to wrap it, to give something similar to move semantics at the call site. Something along the lines of

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));

Alternatively, Boost has a library to allow move semantics without C++11.



回答2:

You may use some wrapper with explicit name:

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); }

And then

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;
};

usage:

std::vector<int> v(100, 1337);
Foo foo(myMove(v));


回答3:

Sure it is possible to have move semantics in C++03.

Using 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));
}

Or you can write it on your own, that works with l-values, temporaries, and where r_value_ref<T> wrapper can be used as T& or const T& thanks to 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));
}

Live demo link

However, it's unsafe, e.g. r_value_ref<int> i(123); i.get() = 456; compiles successfully, but leads to undefined behavior. Hand-written r-value-reference wrappers require a little more effort to make them perfectly safe.



回答4:

If I get you right, you want to "move" the vector in a class member via a constructor call.

In C++11 you would have to provide a constructor with std::vector<T>&& argument and call

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

In C++03 you can add a "named constructor" or a friend which does this job for you.

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;
};

Where you could use it like this:

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

In this way it is clear that the vector is moved and the move-syntax does not differ that much from C++11 (although being non-generic of course).

Note that this will copy Foo if optimization is turned off.



回答5:

C++03 way is to use std::auto_ptr to express passing ownership of the data

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 in signature clearly states that data is passed inside the function, leaving call site with empty pointer, due to the nature of copying an auto_ptr.

Note the need to either disallow or explicitly define correct copy-constructing and assignment of such a class, since default copy constructor and assignment of auto_ptr is usually not what would be correct.