-1

I've been trying to nail down the rule of 5, but most of the information online is vastly over-complicated, and the example codes differ.

Even my textbook doesn't cover this topic very well.

On move semantics:

Templates, rvalues and lvalues aside, as I understand it, move semantics are simply this:

int other     = 0;           //Initial value
int number    = 3;           //Some data

int *pointer1 = &number;     //Source pointer
int *pointer2 = &other;      //Destination pointer

*pointer2     = *pointer1;   //Both pointers now point to same data 
 pointer1     =  nullptr;    //Pointer2 now points to nothing

//The reference to 'data' has been 'moved' from pointer1 to pointer2

As apposed to copying, which would be the equivalent of something like this:

pointer1      = &number;     //Reset pointer1

int newnumber = 0;           //New address for the data

newnumber     = *pointer1;   //Address is assigned value
pointer2      =  &newnumber; //Assign pointer to new address

//The data from pointer1 has been 'copied' to pointer2, at the address 'newnumber'

No explanation of rvalues, lvalues or templates is necessary, I would go as far as to say those topics are unrelated.

The fact that the first example is faster than the second, should be a given. And I would also point out that any efficient code prior to C++ 11 will do this.

To my understanding, the idea was to bundle all of this behavior in a neat little operator move() in std library.

When writing copy constructors and copy assignment operators, I simply do this:

Text::Text(const Text& copyfrom) {
    data  = nullptr;  //The object is empty
    *this = copyfrom;

}


const Text& Text::operator=(const Text& copyfrom) {
    if (this != &copyfrom) {
        filename = copyfrom.filename;
        entries  = copyfrom.entries;

        if (copyfrom.data != nullptr) {  //If the object is not empty
            delete[] data;
        }

        data = new std::string[entries];

        for (int i = 0; i < entries; i++) {
            data[i] = copyfrom.data[i];
            //std::cout << data[i];
        }
        std::cout << "Data is assigned" << std::endl;

    }

    return *this;
}

The equivalent, one would think, would be this:

Text::Text(Text&& movefrom){
    *this = movefrom;
}

Text&& Text::operator=(Text&& movefrom) {
    if (&movefrom != this) {
        filename = movefrom.filename;
        entries  = movefrom.entries;
        data     = movefrom.data;

        if (data != nullptr) {
            delete[] data;
        }

        movefrom.data    = nullptr;
        movefrom.entries = 0;
    }
    return std::move(*this);
}

I'm quite certain this won't work, so my question is: How do you achieve this type of constructor functionality with move semantics?

bigcodeszzer
  • 916
  • 1
  • 8
  • 27
  • Your two "move" examples (first two lines of code) won't compile (first) and don't move anything (first & second)... – Mat Feb 09 '16 at 13:03
  • The principle behind move sematics is that you can effeciently move data to somewhere else without copying. You can do this without undefined behaviour if the previous owner of the data can not be reached from within the program (unnamed variable) – Neijwiert Feb 09 '16 at 13:08
  • @Neijwiert Fair enough, but in the case of move constructors and move assignment operators, the previous owner can still be reached. – bigcodeszzer Feb 09 '16 at 13:23
  • `*pointer1 = *pointer2;` This will likely crash (or at least give undefined behaviour), because `pointer2` is uninitialised. – psmears Feb 09 '16 at 13:26
  • @psmears Oups, sorry, I wrote it backwards. – bigcodeszzer Feb 09 '16 at 13:27
  • 1
    No it's not fixed, still crashes, you're dereferencing pointer2 without having initialized it. Even if what you wanted to write was what the comment says, that still wouldn't move anything anywhere. – Mat Feb 09 '16 at 13:34
  • @Mat I'll fix that, one second. – bigcodeszzer Feb 09 '16 at 13:37
  • My professor explained move semantics this way. If nothing is moving in this case, how does the move operator actually 'move' anything, comparably? – bigcodeszzer Feb 09 '16 at 13:38
  • It would be helpful to have the class definition, but I must assume that `data` is dynamically allocated also in your classical copy ctor. Then `*this = copyfrom` just duplicates the pointer. Later destruction of the two objects would try to free data twice -> crash. Typically you must allocate duplicate space in the target (unless you have smart pointers, copy on write or the like). This "embarassing" (Stroustrup) inefficiency when `copyfrom` is a temporary is exactly what moving addresses. You can't demonstrate that without demonstrating the classical inefficiency first :-). – Peter - Reinstate Monica Feb 09 '16 at 13:43
  • 1
    And the crucial part in your move ctor is missing: A simple assignment `data = movefrom.data`. No re-allocation and deep copy in the target, as opposed to a non-moving copy ctor. – Peter - Reinstate Monica Feb 09 '16 at 13:49
  • @PeterA.Schneider Fixed the copy constructor. Sorry, I'm copying and pasting things from old projects that I don't remember writing anymore. – bigcodeszzer Feb 09 '16 at 14:01
  • _How do you achieve this type of copy functionality with move semantics?_ The point is to _not_ copy. – erip Feb 09 '16 at 14:04
  • @PeterA.Schneider Added the data=movefrom.data .... I think this is why I mixed the two up a bit. – bigcodeszzer Feb 09 '16 at 14:05
  • @erip By functionality, I mean calling the the move assignment operator from the move constructor the way I did with copy example. – bigcodeszzer Feb 09 '16 at 14:06
  • @Mat The first two examples have been fixed. – bigcodeszzer Feb 09 '16 at 14:26
  • Move assignment operator should not return an r-value reference... this line is highly suspect in your move assignment operator definition `return std::move(*this);` – Chris Beck Feb 09 '16 at 14:34
  • @ChrisBeck That was one of the things that was bugging me. They used that line in an example in my textbook. – bigcodeszzer Feb 09 '16 at 14:43
  • Which textbook are you using? – Peter - Reinstate Monica Feb 09 '16 at 14:52
  • https://scs.senecac.on.ca/~oop345/pages/content/compt.html – bigcodeszzer Feb 09 '16 at 15:05
  • Under section Move-Constructor and Move-Assignment Operator – bigcodeszzer Feb 09 '16 at 15:05
  • http://stackoverflow.com/questions/3106110/what-are-move-semantics – Johannes Feb 09 '16 at 18:12

1 Answers1

0

It's not entirely clear to me what is supposed to be proved by your code examples -- or what the focus is of this question is.

Is it conceptually what does the phrase 'move semantics' mean in C++?

Is it "how do I write move ctors and move assignment operators?" ?

Here is my attempt to introduce the concept. If you want to see code examples then look at any of the other SO questions that were linked in comments.


Intuitively, in C and C++ an object is supposed to represent a piece of data residing in memory. For any number of reasons, commonly you want to send that data somewhere else.

Often one can take a direct approach of simply passing a pointer / reference to the object to the place where the data is needed. Then, it can be read using the pointer. Taking the pointer and moving the pointer around is very cheap, so this is often very efficient. The chief drawback is that you have to ensure that the object will live for as long as is needed, or you get a dangling pointer / reference and a crash. Sometimes that's easy to ensure, sometimes its not.

When it isn't, one obvious alternative is to make a copy and pass it (pass-by-value) rather than passing by reference. When the place where the data is needed has its own personal copy of the data, it can ensure that the copy stays around as long as is needed. The chief drawback here is that you have to make a copy, which may be expensive if the object is big.

A third alternative is to move the object rather than copying it. When an object is moved, it is not duplicated, and instead becomes available exclusively in the new site, and no longer in the old site. You can only do this when you won't need it at the old site anymore, obviously, but in that case this saves you a copy which can be a big savings.

When the objects are simple, all of these concepts are fairly trivial to actually implement and get right. For instance, when you have a trivial object, that is, one with trivial construction / destruction, it is safe to copy it exactly as you do in the C programming language, using memcpy. memcpy produces a byte-for-byte copy of a block of bytes. If a trivial object was properly initialized, since its creation has no possible side-effects, and its later destruction doesn't either, then memcpy copy is also properly initialized and results in a valid object.

However, in modern C++ many of your objects are not trivial -- they may "own" references to heap memory, and manage this memory using RAII, which ties the lifetime of the object to the usage of some resource. For instance, if you have a std::string as a local variable in a function, the string is not totally a "contiguous" object and rather is connected to two different locations in memory. There is a small, fixed-size (sizeof(std::string), in fact) block on the stack, which contains a pointer and some other info, pointing to a dynamically sized buffer on the heap. Formally, only the small "control" part is the std::string object, but intuitively from the programmer's point the buffer is also "part" of the string and is the part that you usually think about. You can't copy a std::string object like this using memcpy -- think about what will happen if you have std::string s and you try to copy sizeof(std::string) bytes from address &s to get a second string. Instead of two distinct string objects, you'll end up with two control blocks, each pointing to the same buffer. And when the first one is destroyed, that buffer is deleted, so using the second one will cause a segfault, or when the second one is destroyed, you get a double delete.

Generally, copying nontrivial C++ objects with memcpy is illegal and causes undefined behavior. This is because it conflicts with one of the core ideas of C++ which is that object creation and destruction may have nontrivial consequences defined by the programmer using ctors and dtors. Object lifetimes may be used to create and enforce invariants which you use to reason about your program. memcpy is a "dumb" low-level way to just copy some bytes -- potentially it bypasses the mechanisms that enforce the invariants which make your program work, which is why it can cause undefined behavior if used incorrectly.

Instead, in C++ we have copy constructors which you can use to safely make copies of nontrivial objects. You should write these in a way that preserves what invariants you need for your object. The rule of three is a guideline about how to actually do that.

The C++11 "move semantics" idea is a collection of new core language features which were added to extend and refine the traditional copy construction mechanism from C++98. Specifically, it's about, how do we move potentially complex RAII objects, not just trivial objects, which we already were able to move. How do we make the language generate move constructors and such for us automatically when possible, similarly to how it does it for copy constructors. How do we make it use the move options when it can to save us time, without causing bugs in old code, or breaking core assumptions of the language. (This is why I would say that your code example with int's and int *'s has little to do with C++11 move semantics.)

The rule of five, then, is the corresponding extension of the rule of three which describes conditions when you may need to implement a move ctor / move assignment operator also for a given class and not rely on the default behavior of the language.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • My question is: How do you write them. And there are discrepancies between stack questions, online information and my text book. – bigcodeszzer Feb 11 '16 at 20:47
  • By them, I'm referring to move operators and move constructors. – bigcodeszzer Feb 11 '16 at 20:47
  • As for the dangling pointer thing, I may have just not understand the situation where this would be a problem. That big said, even move semantics turn the source Pointer to null, so doing this should eliminate that problem. As far as stability, I would never reference a dynamic block without first testing if it's null. So I don't really understand what you mean? – bigcodeszzer Feb 11 '16 at 20:50
  • Well how exactly you write them depends on exactly what resource is being held, what invariants the class has, and so on. The main idea is 1. If A is move constructed / move assigned from B, then A should be identical to B in all respects you consider important afterwards. Similar to if you had copy constructed it. 2. You are allowed to modify B in any way, but you must leave it in a "valid state". The lifetime of B is not over immediately after it has been moved from, it's dtor will still be called later, and if that will cause a crash then your move op is flawed. – Chris Beck Feb 11 '16 at 22:04
  • I guess one way to think about it is, first take your copy ctor implementation, which you already know how to do correctly. Now think, how could I do the same thing faster if I know that it's ok to pilfer resources from the input instead of making copies. If there are optimization opportunities, then you can code the move constructor that way and get a speed-up in your program. That's all that's going on. It's really the same as thinking about, how do I implement the copy ctor, it's just that the post-condition you have to live up to is relaxed. – Chris Beck Feb 11 '16 at 22:08