0
#include <iostream>
#include <utility>
#include <vector>

class Node
{
public:
    int data;
    Node* prev;
    Node* next;
};

class Doublyll
{
private:
    Node* head;
    Node* tail;
public:
    Doublyll();
    Doublyll(std::vector<int> V);
    Doublyll(const Doublyll& source);
    Doublyll(Doublyll&& src) noexcept;
    Doublyll& operator=(const Doublyll& rhs);
    Doublyll& operator=(Doublyll&& src) noexcept;
    ~Doublyll();

    friend std::ostream& operator<<(std::ostream& os, const Doublyll& src);

    void Concatenate(Doublyll&& l2);
};

// Default Constructor will SET head and tail to nullptr
Doublyll::Doublyll()
    : head(nullptr), tail(nullptr)
{
}

// Explicit Constructor using vector
Doublyll::Doublyll(std::vector<int> V)
    : head(nullptr), tail(nullptr)
{
    Node** p = &head;

    for (auto& value : V)
    {
        Node* t = new Node;
        t->data = value;

        if (head == nullptr)
            t->prev = nullptr;
        else
            t->prev = tail;
        t->next = nullptr;

        *p = t;
        p = &(t->next);

        tail = t;
    }
}

// Copy Constructor
Doublyll::Doublyll(const Doublyll& source)
    : head(nullptr), tail(nullptr)
{
    std::cout << "Copy Construcor called!\n";

    Node** p = &head;

    // Iterate through all Node in source linked list, copying it to new object
    for (Node* tmp = source.head; tmp != NULL; tmp = tmp->next)
    {
        Node* t = new Node;
        t->data = tmp->data;
        if (head == nullptr)
            t->prev = nullptr;
        else
            t->prev = tail;
        t->next = nullptr;

        *p = t;
        p = &(t->next);

        tail = t;
    }
}

// Move Constructor
Doublyll::Doublyll(Doublyll&& src) noexcept
    : head(std::exchange(src.head, nullptr)), tail(std::exchange(src.tail, nullptr))
{
    std::cout << "Move Constructor called!\n";
}

// Copy Assignment Operator
Doublyll& Doublyll::operator=(const Doublyll& rhs)
{
    std::cout << "Copy Assignment Operator called!\n";

    // Check self assignment
    if (this != &rhs)
    {
        Doublyll tmp(rhs);
        std::swap(tmp.head, head);
        std::swap(tmp.tail, tail);
    }
    return *this;
}

// Move Assignment
Doublyll& Doublyll::operator=(Doublyll&& src) noexcept
{
    std::cout << "Move Assignment called!\n";

    if (this != &src)
    {
        std::swap(head, src.head);
        std::swap(tail, src.tail);
    }
    return *this;
}

// Destructor
Doublyll::~Doublyll()
{
    std::cout << "Desctructor called @ address " << &head << std::endl;

    Node* p = head;
    Node* tmp;

    while (p != nullptr)
    {
        tmp = p;
        p = p->next;
        delete tmp;
    }
}

// Display using Overloading << Operator
std::ostream& operator<<(std::ostream& os, const Doublyll& src)
{
    Node* tmp = src.head;

    if (tmp == NULL)
        std::cout << "(EMPTY)\n";
    else
    {
        for (; tmp != nullptr; tmp = tmp->next)
            std::cout << tmp->data << " ";
        std::cout << std::endl;
    }

    return os;
}

void Doublyll::Concatenate(Doublyll&& l2)
{
    // Since we have tail, we can connect it by using it
    tail->next = l2.head;
    l2.head->prev = tail;

    // Move first Node's tail to last Node of second linked list
    tail = l2.tail;
    
    // Make l2 as NULL
    /*l2.head = l2.tail = nullptr;*/
}

int main()
{
    // Create an Vector
    std::vector<int> v1 = { 1, 3, 5, 7, 9, 11 };
    std::vector<int> v2 = { 2, 4, 6, 8 };

    // Create object and linked list
    Doublyll l1(v1);
    Doublyll l2(v2);

    // Display linked list
    std::cout << l1;
    std::cout << l2;

    // Concatenate 2 linked list
    l1.Concatenate(std::move(l2));

    // Display agaian after concatenate, l1 should connect with l2. and l2 should be EMPTY
    std::cout << l1;
    std::cout << l2;

    std::cin.get();
}

In this code I tried to Concatenate 2 Linked List that I've created using Vector.

In main(), I called by using std::move l1.Concatenate(std::move(l2)); because at the end, after l1 connect with l2, I want l2 become NULL to prevent Double Free when Destructor is called. But, it seems like either move constructor or move assignment that I've created are not being executed.

Here's the thing:

  1. I know I don't need using std::move(l2), I can simply pass l1.Concatenate(l2) by Reference void Doublyll::Concatenate(Doublyll& l2). And it worked. But I want to practice using move constructor and assignment.
  2. You can see in void Doublyll::Concatenate(Doublyll&& l2) there's this code I've comment: /*l2.head = l2.tail = nullptr;*/ which I've done manually to make l2 as NULL. It will worked perfectly and not cause double free. But, if I do std::move(l2) I don't need to done it manually, right?
  3. My move constructor and move assignment is actually working if I moving old object to new object like this: Doublyll l3(std::move(l2)); and like this l2 = std::move(l1);

So, why do you think it's not working when I try to call it like this l1.Concatenate(std::move(l2)); to concatenate two linked list? I thought it's going to be like move l2 to l1 before make l2 inaccessible (NULL).

Is std::move doesn't work like that? or maybe there's something wrong in my code or there's another way that still using std::move to perform a function like that? and maybe you can also explain me about this move semantics. Thank You.

Kevinkun
  • 21
  • 5
  • 1
    That looks like a LOT of code to demonstrate behavior involving `std::move`. Did you try to pare it down to something simpler? Focus on the line with `std::move` and keep only what is needed to demonstrate your point. Try to think more abstractly. Don't worry about linked lists and related functionality; focus on your diagnostic messages that indicate what is (and is not) being called. You have a class, call it `A`, that you want to move. Class `A` has no functionality other than tracking member function calls. Can you demonstrate your question with `A`? – JaMiT Jul 23 '21 at 06:02
  • 1
    I also recommend reviewing [ask], in particular the part about presenting your question *before* your code. Forcing yourself to describe your situation in words often brings greater understanding, even if no one answers your question. – JaMiT Jul 23 '21 at 06:06
  • 1
    *"maybe you can also explain me about this move semantics."* -- if this is your real question (remember: one question per question), then you're probably duplicating [What is move semantics?](https://stackoverflow.com/questions/3106110) Another potential duplicate: [What is `std::move()`, and when should it be used?](https://stackoverflow.com/questions/3413470) Do either of these answer your question? – JaMiT Jul 23 '21 at 06:08
  • Give a look at [What's the difference between std::move and std::forward](https://stackoverflow.com/questions/9671749/whats-the-difference-between-stdmove-and-stdforward/65446786#65446786). That should clarify a lot what `std::move` is and what it is for. – Enlico Jul 23 '21 at 06:54

1 Answers1

0

The code doesn't work because you commented out the part that makes it work, namely

l2.head = l2.tail = nullptr;

You don't see the move constructor or move assignment operators being used because you have not written any code that uses them. Did you expect something here to call them?

I'm not sure what your question is, but I'm guessing you have a fundamental misunderstanding of what move semantics are. The only thing that std::move does -- ever -- is indicate that something is allowed to be passed to a function that takes a T&& as an argument. Your responsibility when you write a function that takes a T&& (a.k.a. an "rvalue reference") is to move the guts out of the thing you were passed and leave it in a state where all it can do is destruct without causing any problems. This is precisely what you did when you manually set the head pointer to nullptr.

Daniel McLaury
  • 4,047
  • 1
  • 15
  • 37
  • I don't think you read my statements above properly. I know the code: `l2.head = l2.tail = nullptr;` is worked. But I commented out because I think if use move semantics it'll automatically set object `l2` to `NULL`. So I don't need to do that manually. – Kevinkun Jul 23 '21 at 05:05
  • And I also know `std::move` is pass to a function with `&&` as paramater. Look at my function: `void Doublyll::Concatenate(Doublyll&& l2)`. And I also create move constructor and move assignment to handle it. – Kevinkun Jul 23 '21 at 05:05
  • "And I also know std::move is pass to a function with && as paramater." Yes, but you don't seem to understand that this is *all* it does, because you seem to think that writing `std::move` is going to somehow call a move constructor or move assignment operator. – Daniel McLaury Jul 23 '21 at 05:12
  • So what you mean std::move is not going to call a move constructor or assignment? Because when you use it to move an object to new or existing object, of course it will call move constructor/assignment. Like: LinkedList l2(std::move(l1)) we move l1 to l2. So what is your point? – Kevinkun Jul 23 '21 at 06:22
  • A move constructor will be called if (1) you do something that calls a constructor; (2) a move constructor exists; and (3) the constructor argument is an rvalue reference. Similarly, a move-assignment operator will be called if (1) you do something that does an assignment; (2) a move-assignment operator exists; and (3) the thing you're assigning is an rvalue reference. – Daniel McLaury Jul 23 '21 at 06:26
  • You have written a move constructor and a move assignment operator, so (1) is taken care of. But the only time you call a constructor the constructor argument is a vector, and you never perform an assignment at all. – Daniel McLaury Jul 23 '21 at 06:30