【问题标题】:Move Semantics: Constructor and Assignment are not executed when performed std::move移动语义:执行 std::move 时不执行构造函数和赋值
【发布时间】:2021-07-23 04:28:38
【问题描述】:
#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();
}

在这段代码中,我尝试连接我使用 Vector 创建的 2 个链接列表

在 main() 中,我使用 std::move l1.Concatenate(std::move(l2)); 调用,因为最后,在 l1 与 l2 连接之后,我希望 l2 变为NULL 以防止在调用析构函数时 Double Free。但是,似乎我创建的移动构造函数或移动赋值都没有被执行。

事情是这样的:

  1. 我知道我不需要使用 std::move(l2),我可以通过引用 void Doublyll::Concatenate(Doublyll&amp; l2) 简单地传递 l1.Concatenate(l2)。它奏效了。但我想练习使用移动构造函数和赋值。
  2. 您可以在void Doublyll::Concatenate(Doublyll&amp;&amp; l2) 中看到我评论过的这段代码:/*l2.head = l2.tail = nullptr;*/,我已经手动将l2 设为NULL。它会完美运行,不会导致双重释放。但是,如果我执行 std::move(l2) 我不需要手动完成,对吧?
  3. 如果我像这样将旧对象移动到新对象,我的移动构造函数和移动赋值实际上是有效的:Doublyll l3(std::move(l2)); 和这样的l2 = std::move(l1);

那么,当我尝试像这样调用l1.Concatenate(std::move(l2)); 来连接两个链表时,为什么你认为它不起作用?我认为这就像在使 l2 不可访问(NULL)之前将 l2 移动到 l1。

std::move 不是那样工作的吗?或者我的代码可能有问题,或者还有另一种方式仍然使用 std::move 来执行这样的功能?也许你也可以向我解释一下这个移动语义。谢谢。

【问题讨论】:

  • 这看起来有很多代码来演示涉及std::move 的行为。您是否尝试将其简化为更简单的东西?专注于std::move 并仅保留证明您的观点所需的内容。尝试更抽象地思考。不用担心链表和相关功能;专注于您的诊断消息,这些消息表明正在调用(和未调用)什么。你有一个班级,叫它A,你想搬家。 A 类除了跟踪成员函数调用外没有其他功能。你能用A 证明你的问题吗?
  • 我还建议查看How to Ask,尤其是关于在代码之前提出问题的部分。即使没有人回答你的问题,强迫自己用语言描述你的情况通常会带来更多的理解。
  • “也许你也可以向我解释一下这个移动语义。”——如果这是你真正的问题(记住:每个问题一个问题),那么你可能是重复What is move semantics? 另一个潜在的重复:What is std::move(), and when should it be used? 这些是否回答了您的问题?
  • 看看What's the difference between std::move and std::forward。这应该清楚地说明std::move 是什么以及它的用途。

标签: c++ linked-list move-semantics


【解决方案1】:

代码不起作用,因为您注释掉了使它起作用的部分,即

l2.head = l2.tail = nullptr;

您没有看到使用移动构造函数或移动赋值运算符,因为您没有编写任何使用它们的代码。你期待这里有什么东西会打电话给他们吗?

我不确定你的问题是什么,但我猜你对什么是移动语义有一个基本的误解。 std::move 所做的唯一一件事——曾经——是表明允许将某些东西传递给一个以T&amp;&amp; 作为参数的函数。当您编写一个采用T&amp;&amp;(又名“右值引用”)的函数时,您的责任是将内脏从您传递的东西中移出,并使其处于一种状态,它所能做的就是破坏而不会造成任何问题.这正是您手动将头指针设置为nullptr 时所做的。

【讨论】:

  • 我认为您没有正确阅读我上面的陈述。我知道代码:l2.head = l2.tail = nullptr; 有效。但我注释掉了,因为我认为如果使用移动语义,它会自动将对象l2 设置为NULL。所以我不需要手动操作。
  • 我也知道std::move 被传递给一个以&amp;&amp; 为参数的函数。看看我的函数:void Doublyll::Concatenate(Doublyll&amp;&amp; l2)。我还创建了移动构造函数和移动赋值来处理它。
  • “而且我还知道 std::move 是传递给以 && 为参数的函数。”是的,但您似乎不明白这就是它所做的全部,因为您似乎认为编写std::move 会以某种方式调用移动构造函数或移动赋值运算符。
  • 那么你的意思是 std::move 不会调用移动构造函数或赋值?因为当您使用它将对象移动到新对象或现有对象时,它当然会调用移动构造函数/赋值。像:LinkedList l2(std::move(l1)) 我们将 l1 移动到 l2。那你的意思是什么?
  • 如果 (1) 你做了一些调用构造函数的事情,移动构造函数将被调用; (2) 存在移动构造函数; (3) 构造函数参数是一个右值引用。类似地,如果 (1) 你做了一些赋值操作,移动赋值运算符将被调用; (2) 存在移动赋值运算符; (3) 你分配的东西是一个右值引用。
猜你喜欢
  • 2015-02-14
  • 2014-03-13
  • 2020-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-28
  • 2013-12-08
  • 2020-10-03
相关资源
最近更新 更多