简体   繁体   English

移动语义:执行 std::move 时不执行构造函数和赋值

[英]Move Semantics: Constructor and Assignment are not executed when performed std::move

#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.在这段代码中,我尝试连接使用 Vector 创建的2 个链表

In main(), I called by using std::move l1.Concatenate(std::move(l2));在 main() 中,我使用std::move l1.Concatenate(std::move(l2));调用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.因为最后,在 l1 与 l2 连接后,我希望l2变为 NULL 以防止调用析构函数时出现双重释放 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) .我知道我不需要使用std::move(l2) ,我可以简单地通过 Reference void Doublyll::Concatenate(Doublyll& l2)传递l1.Concatenate(l2) 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.你可以在void Doublyll::Concatenate(Doublyll&& l2)看到这段代码,我已经注释了: /*l2.head = l2.tail = nullptr;*/我已经手动完成将l2设为 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?但是,如果我执行 std::move(l2) 我不需要手动完成,对吗?
  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));如果我像这样将旧对象移动到新对象,我的移动构造函数和移动赋值实际上是有效的: Doublyll l3(std::move(l2)); and like this l2 = std::move(l1);像这样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));那么,当我尝试像这样调用它时,为什么你认为它不起作用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).我认为这就像在使 l2 无法访问(NULL)之前将 l2 移动到 l1。

Is std::move doesn't work like that? std::move不是这样工作的吗? 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?或者我的代码可能有问题,或者还有另一种方法仍然使用std::move来执行这样的功能? and maybe you can also explain me about this move semantics .也许你也可以向我解释这个移动语义 Thank You.谢谢。

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. std::move所做的唯一一件事——永远——表明允许将某些东西传递给一个将T&&作为参数的函数。 Your responsibility when you write a function that takes a T&& (aka 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.当您编写一个接受T&& (又名“右值引用”)的函数时,您的责任是将传递的内容移出并使其处于一种状态,在这种状态下,它只能进行破坏而不会造成任何问题。 This is precisely what you did when you manually set the head pointer to nullptr .这正是您手动将头指针设置为nullptr时所做的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM