0

I am trying to understand move semantics and in particular how std::move() works.

I understood that it's basically a static-cast to an rvalue reference type, but this exercise gets me confused. I have implemented a Node class following the Rule of Five (I know I could have followed the copy and swap idiom for a better implementation).

class Node
{
    public:
        Node(char data = ' ', Node *left_child = NULL, Node *right_child = NULL) : _data(data), _left_child(left_child), _right_child(right_child)
        {
            std::cout << "NODE CONSTRUCTOR" << std::endl;
        }
        Node(const Node& node);
        Node(Node&& node) noexcept;
        GetData() const;
        Node& operator=(const Node &n);
        Node& operator=(Node&& node) noexcept;
        ~Node();

    protected:
        Node *_left_child;
        Node *_right_child;
        char _data;
};

char Node::GetData() const
{
    return _data;
}

Node::Node(const Node& node)
{
    ...
}

Node::Node(Node&& node) noexcept
{
    std::cout << "MOVE CONSTRUCTOR" << std::endl;
    this->_data = node.GetData();
    this->_left_child = node._left_child;
    this->_right_child = node._right_child;

    node._right_child = nullptr;
    node._left_child = nullptr;
}

Node& Node::operator=(Node&& node) noexcept
{
    std::cout << "MOVE ASSIGNMENT OPERATOR " << std::endl;
    if(&node != this)
    {
        if(this->_right_child != nullptr)
        {
            delete this->_right_child;
        }
        if(this->_left_child != nullptr)
        {
            delete this->_left_child ;
        }
    }
    this->_data = node.GetData();
    this->_left_child = node._left_child;
    this->_right_child = node._right_child;

    node._right_child = nullptr;
    node._left_child = nullptr;

    return *this;
    
}

Node::~Node() 
{
    delete _left_child;
    delete _right_child;
}

Node& Node::operator=(const Node &n)
{
    ...
}

Then in my main() function:

int main() {

    Node *NodeOne = new Node{};
    Node NodeTwo{};

    std::stack<Node*> stack_of_nodes_ptr;
    stack_of_nodes_ptr.push(std::move(NodeOne));

    delete stack_of_nodes_ptr.top();
    stack_of_nodes_ptr.pop();

    std::stack<Node> stack_of_nodes;
    stack_of_nodes.push(std::move(NodeTwo));

    return 0;
}

The output is:

NODE CONSTRUCTOR
NODE CONSTRUCTOR
CALLING NODE BASE DESTRUCTOR  
MOVE CONSTRUCTOR
CALLING NODE BASE DESTRUCTOR  
CALLING NODE BASE DESTRUCTOR

My doubt arises seeing that the move constructor is not called in the first push_back() but only in the second one. The only difference here is that the first stack is of Node* pointers while the other one is a stack of Node objects.

Could you please tell me why, in case of raw pointer, move constructor is not called?

nick41
  • 11
  • 2
  • 1
    Why would it be called? You only "moved" a pointer. You didn't move a node. – user253751 Mar 21 '22 at 17:35
  • The value of a pointer is which object it points to, an address. Moving a pointer moves that value, the address, it doesn't move the pointed object. The same way copying a pointer doesn't copy the pointed object, moving a pointer doesn't move the pointed object. – François Andrieux Mar 21 '22 at 17:36
  • 1
    you are not using smart pointers so move schematics do not give you anything. – Marek R Mar 21 '22 at 17:37
  • 1
    You may need to distinguish between "constructor is called" and "std::move is called". You say one didn't happen and therefore the other didn't happen - which is an incorrect assumption. You are certainly calling `std::move` on your raw pointer, but copying and moving raw pointers will never invoke a constructor. – Drew Dormann Mar 21 '22 at 17:38
  • The idea of "move semantics" is that you don't have to copy over a big object if you don't need it at the old location anymore. I.e. if an object holds a pointer to a big dynamically allocated object, you don't copy the whole big object, you just make the pointer in the new holding object point to the existing big object. But the pointer value itself will still be copied. There's no avoiding that, but that's not so costly. – JHBonarius Mar 21 '22 at 18:07
  • Thanks to all of you for the quick reply. @DrewDormann why raw pointers do not invoke a constructor? Actually that was my question, I made a mistake mentioning std::move instead of move constructor. – nick41 Mar 23 '22 at 16:33
  • @nick41 the purpose of a _constructor_ is to construct a new object. Copying a pointer to `Node` will make a new pointer. It will not make a new `Node`. If a metaphor helps -- if I have the postal address to your house and I make a copy of that address, another house doesn't get constructed. Only the address was copied, to the same already-existing house. – Drew Dormann Mar 23 '22 at 16:40
  • 1
    @DrewDormann with your example, it makes a lot of sense now. And I think the same behaviour can be applied to the move constructor as well. Thanks a lot! – nick41 Mar 23 '22 at 17:05

1 Answers1

3

Could you please tell me why in case of raw pointer std::move is not called?

std::move is called. But std::move doesn't print anything. Remember that std::move is:

basically a static cast to an rvalue reference type

Calling std::move never causes any output.


My doubt arises seeing that the move constructor is not called in the first push_back

You didn't move an object of type Node, so the move constructor of Node was not called.

For all fundamental types, moving is same as copying. Pointers are fundamental types.

eerorika
  • 232,697
  • 12
  • 197
  • 326