2

I'm trying to understand move semantics, and wrote some simple code to exemplify different types of methods according to the C++ rule of five, but I'm encountering a practical issue regarding the move constructor of a custom class myClass:

#include <vector>
#include <iostream>

class myClass{
    std::vector<float> data;
public:
    //ctors
    myClass() : data (std::vector<float> (1,0.0)) { 
        printf("empty ctor\n");
    };
    myClass(const myClass& _other) { 
        printf("copy ctor\n");
        data=_other.data; 
    };
    myClass(myClass&& _other) { 
        printf("move ctor\n");
        if (this != &_other) {
            data=_other.data;
            _other.data.clear();
        }
    };
    //dtor
    ~myClass() { data.clear(); };
    //op overloads
    myClass& operator=(const myClass& _other) {
        printf("copy assignment\n");
        data=_other.data; 
        return *this;
    };
    myClass& operator=(myClass&& _other) {
        printf("move assignment\n");
        if (this != &_other) {
            data=_other.data;
            _other.data.clear();
        }
        return *this;
    };
    //gp members
    void push_val(float _val) {  data.push_back(_val);  };
    float get_val(int _ind) {  return data[_ind]; };
};

myClass myFunc() { 
    myClass FF;
    return FF; 
};

int main(int argc, char const *argv[]){
    // empty ctor:
    myClass A; 
    // copy ctor:
    A.push_val(1.0);
    myClass B = A; 
    // move ctor:
    myClass C = myFunc(); 
    myClass D = std::move(myFunc()); 
    // copy assignment:
    A = C; 
    // move assignment:
    B = myFunc(); 

    return 0;
}

which provides the output:

empty ctor
copy ctor
empty ctor
empty ctor
move ctor
copy assignment
empty ctor
move assignment

which is mostly what was expected, with the exception of one particular case: when instantiating C, which assigns the output of myFunc() (which I was assuming to be an rvalue), the empty constructor of myClass is called instead of the move constructor. Weirdly, when testing the move assignment operator with B = myFunc(), the out put is as expected. What exactly am I missing here?

joaocandre
  • 1,621
  • 4
  • 25
  • 42
  • 1
    Implementing move construction or assignment using `data=_other.data;` is not taking advantage of `std::vector`'s support for move semantics. – François Andrieux Nov 01 '18 at 18:39
  • Not really a duplicate but an adequate referencing redirection. – Lightness Races in Orbit Nov 01 '18 at 18:39
  • 1
    If you don't need to implement any operator or constructor for your type. It would automatically fully and correctly support move semantics since it's only member is a `std:::vector`. – François Andrieux Nov 01 '18 at 18:40
  • However you wouldn't get the tracking debug output. – Lightness Races in Orbit Nov 01 '18 at 18:41
  • @FrançoisAndrieux _"Tip : In a constructor you can be 100% certain that the argument you get is not a reference to `this`."_ [Really?](http://coliru.stacked-crooked.com/a/16422284ff5dd0ad) (Okay, granted, it can't be a _reference_ because that pointer can't be legally dereferenced until construction is done... though you could still [observe it](http://coliru.stacked-crooked.com/a/9285a0b539c048f8) without realising your program has UB) – Lightness Races in Orbit Nov 01 '18 at 18:42
  • @LightnessRacesinOrbit My comment was too general. I meant to say that the constructors (the ones shown) won't be called with `*this`. Though I admit I hadn't considered the possibility of inheriting from `myClass`. – François Andrieux Nov 01 '18 at 18:47
  • @FrançoisAndrieux I'm just being picky to have some fun :P – Lightness Races in Orbit Nov 01 '18 at 18:49
  • So I've understood the compiler performed copy elision, in order to avoid unnecessary copies - fair enough. But that leaves me a bit lost as to when exactly do I need to write copy and move constructors and assignment overloads for my particular classes, which is something resources on move semantics often fail to clarify. – joaocandre Nov 01 '18 at 19:35
  • @FrançoisAndrieux so would the optimal way be `data=std::move(_other.data)`? – joaocandre Nov 01 '18 at 19:40
  • 1
    @joaocandre Yes. And moving from a vector sets it's size to 0 to you don't need to `clear` it. Though keep in mind that this is what the compiler generated move constructor and move assignment operator would do anyway. You only need to write your own if your class manages a resource like a raw owning pointer, a raw file handle, a raw session handle, any kind of raw unmanaged resource where the default compiler provided constructors and operators would not do the right thing. – François Andrieux Nov 01 '18 at 19:41
  • @FrançoisAndrieux Thanks for the insight, I was unaware of what was the default compiler behaviour. One second thought though, what about functions/members that take const references& as arguments, does the compiler implement any type of move semantics in those cases? – joaocandre Nov 01 '18 at 23:23
  • @joaocandre The compiler only generates [special member functions](https://en.cppreference.com/w/cpp/language/member_functions#Special_member_functions). If you mean when your type is passed to a function that takes a `const` reference than again no, there is no copy or move required when binding to a reference. – François Andrieux Nov 02 '18 at 10:57

0 Answers0