2 Replies - 940 Views - Last Post: 15 December 2015 - 09:34 AM

#1 BetaWar  Icon User is offline

  • #include "soul.h"
  • member icon

Reputation: 1469
  • View blog
  • Posts: 8,176
  • Joined: 07-September 06

Move Semantics

Posted 14 December 2015 - 07:41 PM

Last week I posted a thread regarding more advanced scope guard functionality, and said that if you combine that with move semantics, you could get some interesting transactional programming going.

So today I am going to cover move semantics and the power they bring to the table. The first thing to keep in mind is that it requires a slightly different way of doing things. You have a new example of a double-reference parameter, which is the r-value and what C++ now returns when you do various things (I won't go into all of them, but for instance stringC = stringA + stringB;, would create an r-value that is the concatenation of stringA and stringB which can then be moved into stringC).

If you look at std::move you will see a key bit of information:

Quote

Unless otherwise specified, all standard library objects that have been moved from are placed in a valid but unspecified state.

Guaranteeing that the object, after moved, is in a valid state allows us to destruct it without causing all sorts of fun (such as segmentation faults when you have a class with a pointer). Now, you may be wondering how to go about and ensure that the object is left in a valid state? Well, thankfully, C++11 also solved that with the standard; simply call the default constructor from the move constructor prior to swapping the contents. This should ensure that you have a valid object at the end, while also not having to put much extra work into your class definition.

In my time playing around with move semantics, I found that almost all cases that I actually moved an object were the majority just copy/ pasted code. As a result, I put together a quick macro to take care of the majority of the work, and only requires you to implement the swap function, which it declares for you so you don't have to know the gory details unless you want to:
#define MOVEABLE(className) \
    className(className&& src): className() { className::swap(*this, src); } \
    className& operator=(className src) { className::swap(*this, src); return *this; } \
    static void swap(className& a, className& b )
#define MOVEABLE_IMPL(className) \
    void className::swap(className& a, className& b )



Basically, it does exactly what I put forth above. We add a move constructor (className&&), which calls the default constructor (so you will have to implement one of those) and then swaps the two object's bodies. It also implements a move-assignment operator which unlike normal assignment operators, actually takes an object instead of a const& to an object, so the copy-constructor should be implemented (though the default copy-constructor should work in almost all cases). Finally, we declare a static swap function which you can either define here and now, or end with a semi-colon, and define later using the MOVEABLE_IMPL macro. Notice that you do actually have to pass the class name to the macro, this is because there is currently no standard/ widely supported __CLASS__ definition that we can use to automatically determine what class we are in (hopefully someday there will be one).

So, how about a quick example of how this may be used?
template<typename T> void gswap(T& a, T& b )
{
    T tmp(a);
    a = b;
    b = tmp;
}

class Testing
{
public:
    Testing(int val = 0)
    {
        mVal = new int(val);
    }
    ~Testing(void)
    {
        delete mVal;
    }
    MOVEABLE(Testing)
    {
        gswap(a.mVal, b.mVal);
    }

    void operator=(int val)
    {
        *mVal = val;
    }
    Testing operator+(const Testing& b )
    {
        return Testing(*mVal + *b.mVal);
    }
    friend ostream& operator<<(ostream& out, const Testing& obj);
private:
    int* mVal;
};



Here we implement a generic swap function gswap, which can easily be swapped out with std::swap, but can be useful if you haven't seen one before (and also allowed me to add additional debug information). Then we create a test class, which implements the MOVEABLE macro. As you can see, it actually doesn't do much, but it shows that you can very easily get everything up and running.

Here is the rest of the file (leaving you with something you can compile and execute once you add in iostream and specify that you are using the standard namespace):
ostream& operator<<(ostream& out, const Testing& obj)
{
    out << *obj.mVal;
    return out;
}

int main(void)
{
    Testing first(42);
    cout << __LINE__ << " -> value = " << first << endl;

    Testing second(std::move(first));
    cout << __LINE__ << " -> first = " << first << ", second = " << second << endl;
    first = second + second;
    cout << __LINE__ << " -> first = " << first << ", second = " << second << endl;

    Testing::swap(first, second);
    cout << __LINE__ << " -> first = " << first << ", second = " << second << endl;

    first = std::move(second);
    cout << __LINE__ << " -> first = " << first << ", second = " << second << endl;

    return 0;
}


When we compile and run an example of the above, with extra debug output you can start to see what each of these lines do, and how interesting it can actually become:

Quote

Testing::Testing(int)
59 -> value = 42
Testing::Testing(int)
static void Testing::swap(Testing&, Testing&)
void gswap(T&, T&) [with T = int*]
Testing::Testing(Testing&&)
62 -> first = 0, second = 42
Testing::Testing(int)
static void Testing::swap(Testing&, Testing&)
void gswap(T&, T&) [with T = int*]
Testing& Testing::operator=(Testing)
Testing::~Testing()
64 -> first = 84, second = 42
static void Testing::swap(Testing&, Testing&)
void gswap(T&, T&) [with T = int*]
67 -> first = 42, second = 84
Testing::Testing(int)
static void Testing::swap(Testing&, Testing&)
void gswap(T&, T&) [with T = int*]
Testing::Testing(Testing&&)
static void Testing::swap(Testing&, Testing&)
void gswap(T&, T&) [with T = int*]
Testing& Testing::operator=(Testing)
Testing::~Testing()
70 -> first = 84, second = 0
Testing::~Testing()
Testing::~Testing()

The most interesting thing, from my perspective, is that we don't have a segmentation fault. Now, that may sound like something pretty minor, but when you start to consider how many objects and are flying around and having data ripped out of them to be put into another object, and that we are using heap space, it starts to get a little more interesting. Also take note that we are using std::move in the code above, and it just magically does the correct thing.

Have you played with move semantics? How have your experiences with them been?

Is This A Good Question/Topic? 1
  • +

Replies To: Move Semantics

#2 Anarion  Icon User is offline

  • The Persian Coder
  • member icon

Reputation: 387
  • View blog
  • Posts: 1,663
  • Joined: 16-May 09

Re: Move Semantics

Posted 15 December 2015 - 08:28 AM

I few months ago I decided to play with move semantics to learn more about them. Alright, not a few months, it was more than a year ago! There is a test project for move semantics on my laptop hidden deep down somewhere :whistling:
Anyways, nice post! Although personally I tend to keep away from macros as much as possible.
Was This Post Helpful? 0
  • +
  • -

#3 BetaWar  Icon User is offline

  • #include "soul.h"
  • member icon

Reputation: 1469
  • View blog
  • Posts: 8,176
  • Joined: 07-September 06

Re: Move Semantics

Posted 15 December 2015 - 09:34 AM

I used to avoid them like the plague, but they do have some really nice uses. One of the most common ones I use these days is actually a "constant generator" that takes a string and generates a constant string at compile time that has a good name associated with it and matches case requirements. That way you don't have a ton of "magic" strings littered throughout your code, and instead use these compile-time constants which will result in a compiler error if you typo one of the strings instead of leaving you to debug what the heck is going on when you fat-fingered a map key somewhere and can't figure out why it is causing a crash.
Was This Post Helpful? 1
  • +
  • -

Page 1 of 1