Figuring out std::move's behaviour without internet access

This weekend I had a problem with std::move() and std::unique_ptr. What I wanted to know was very simple: when you move a to b, does b get destroyed? Or is a simply moved to b, and b left dangling? What if the type of a and b has a std:unique_ptr member? My first thought was "Ofcourse the memory gets managed correctly, otherwise moving would be tedious and cumbersome and that's (probably) not what std::unique_ptr is meant to be.". That sounds logical, but I wasn't a 100% sure.

No problemo you say. A quick google, maybe 2 or 3 stack overflow questions and you should know your definite answer. There's one caveat here however: I was on a train from university to my parents. Then you might say: "Bob, you're just whining, trains in the Netherlands have internet!". But nope, not this specific train. "Ok, then you'll have to resort to the offline docs you ofcourse have with you on your laptop. Not as quick and easy as google, but it should do." Well, I do in fact have SDL2 and C/C++ documentation on my laptop, but I couldn't find it explicitly stated. Maybe it's in there somewhere, but I'm not a person who spends hours in the documentation (mostly). So I made a little test to visualize what happens when you std::move() a into b, with regard to constructing and destructing instances.

Let's first go through the context of the experiment. In my cute utility library Nuts 'n Bolts I have a nnb::Text class, which contains among a lot of state variables (color, text, etc.) a std::unique_ptr pointing to a SDL_Texture. This stores the texture generated with SDL_ttf, and should be deleted when the nnb::Text instance gets deleted. In general this all works, but I was curious about one specific case: what if I move one instance of nnb::Text into another? Does the memory get managed correctly?

I had a feeling it did get managed correctly, but I wanted to be sure. So I made this little test setup:

#include <iostream>
#include <memory>

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

class Dummy {
public:
    Dummy(int id) : id{id} {
        std::cout << "Constructing Dummy " << id << std::endl;
    }

    ~Dummy() {
        std::cout << "Destructing Dummy " << id << std::endl;
    }

private:
    int id;
} ;

class Smarty {
public:
    Smarty(int id) :
    p{make_unique<Dummy>(10 * id)},
    id{id} {
        std::cout << "Constructing Smarty " << id << std::endl;
    }

    ~Smarty() {
        std::cout << "Destructing Smarty " << id << std::endl;
    }

private:
    std::unique_ptr<Dummy> p;
    int id;
} ;

int main() {
    std::cout << "Initializing std::unique_ptrs" << std::endl;

    auto p1 = make_unique<Dummy>(1);
    auto p2 = make_unique<Dummy>(2);
    auto p3 = make_unique<Smarty>(3);
    auto p4 = make_unique<Smarty>(4);

    std::cout << "Moving dummies" << std::endl;

    p2 = std::move(p1);

    std::cout << "Moving smarties" << std::endl;

    p4 = std::move(p3);

    std::cout << "Finished experiment" << std::endl;

    return 0;
}

The code should compile without any additional dependencies, warnings, or errors. I compiled it with MinGW's g++ version 4.8.1 on windows with g++ -std=c++11 test.cpp. I had to include an implementation of make_unique because MinGW's headers don't supply it or something on windows.

Lets go through the code briefly. The two classes of interest are Dummy and Smarty. Dummy merely prints when it is constructed and destructed. Smarty does the same, but it also contains a std::unique_ptr to an instance of Dummy. Each instance also contains an id to distinguish the output in the console. The test program is simple: construct 4 std::unique_ptr, 2 of type Dummy and 2 of type Smarty. Then move one Dummy instance into the other, and do the same for the Smarty instances. If my initial assumption was correct, p2 should print a destruction message, and p4 should print 2 destruction messages (from both Smarty and Dummy instances!). When the program exits, p1 and p3 should also print 3 destruction messages total.

And, what'd you expect, that's exactly what it does:

C:\Users\Bob\Cpp\test>a
Initializing std::unique_ptrs
Constructing Dummy 1
Constructing Dummy 2
Constructing Dummy 30
Constructing Smarty 3
Constructing Dummy 40
Constructing Smarty 4
Moving dummies
Destructing Dummy 2
Moving smarties
Destructing Smarty 4
Destructing Dummy 40
Finished experiment
Destructing Smarty 3
Destructing Dummy 30
Destructing Dummy 1

The whole test case is kind of superfluous, but at least I had proof that I was right! And all that without internet! For me this was a small personal victory, even though looking back at it it seems kind of silly.

Now, only one question remains: where in the cppreference.com docs is this behaviour stated explicitly or from what is it deducable? I know you can deduce this (I did) using common sense combined with the intent of std::unique_ptr, but I think this shouldn't be a prerequisite to predict the working of std::move. If you know, please do not hesitate and enlighten me.

Comments

Comments powered by Disqus