队列(Queue)是否会创建一个副本?

5
如果我将一个现有的对象推入队列:
struct Node {int x; int y;};
std::vector<Node> vec;
vec.push_back(Node(1, 3));

std::queue<Node> q;
q.push(vec[0]);

在最后一行,q 存储了 vec[0]地址(指针或引用,除了对象本身),还是复制了整个 Node 对象到 q 中?

3
请自行查找:在复制构造函数中设置一个断点,或者阅读文档(推荐cppreference.com)。请注意,翻译时保留原意并使语言更加通俗易懂,但不提供任何额外解释。 - Ulrich Eckhardt
3个回答

6

当你分配一个右值引用时,它会被复制。当你分配一个左值引用(临时对象)时,它会被移动。

为了检查,使用复制构造函数/操作符和移动构造函数/操作符重载:

#include <iostream>
#include <vector>
#include <queue>

struct Node {
    int x;
    int y;

    Node(int x, int y) : x(x), y(y)
    {
        std::cout << "constructor" << std::endl;
    }

    Node(Node const & original) : x(original.x), y(original.y)
    {
        std::cout << "copy constructor" << std::endl;
    }

    Node(Node const && original) : x(original.x), y(original.y)
    {
        std::cout << "move constructor" << std::endl;
    }

    Node & operator=(Node const & original) {
        std::cout << "assignment operator" << std::endl;
        if(this != &original) {
            x = original.x;
            y = original.y;
        }
        return *this;
    }

    Node & operator=(Node const && original) {
        std::cout << "move operator" << std::endl;
        if(this != &original) {
            x = original.x;
            y = original.y;
        }
        return *this;
    }
};


int main() {

    std::vector<Node> v;

    Node n(1,3);        // constructor
    Node m(3, 4);       // constructor

    m = n;              // assignment operator
    n = Node(2, 3);     // constructor + move operator

    v.push_back({1,2});     // constructor + move constructor
    v.push_back(n);         // copy constructor

    std::queue<Node> q;
    q.push(v[0]);           // copy constructor

    return 0;
}

1
我学到了很多,谢谢。我不知道有这么多不同的构造函数和运算符。 - Neo
大多数都是隐式的,所以除非你进行动态分配,否则不需要实现它们。移动构造函数和运算符是显式的,因此你必须实现它们,否则将使用它们的复制版本。 - PirklW
顺便说一句,如果你想禁止拷贝构造,也可以删除它们的使用,例如:Node(Node const & original) = delete; - PirklW
什么是 "= delete"?我从未见过。我能否使用 "= delete" 使任何函数消失? - Neo
1
不,delete 指令会告诉编译器不要创建拷贝构造函数/操作符或析构函数的隐式版本。你不能只是“删除”一个函数就让它消失 ;) - PirklW

5

它确实创建了一个副本。事实上,您可以通过覆盖复制或移动构造函数来始终找到副本或移动的位置:

class Node 
{
public:
    Node(int x, int y) { std::cout << "Create node" << std::endl; }
    Node(const Node&) { std::cout << "Copy node" << std::endl; }
    Node(Node&&) { std::cout << "Move node" << std::endl; }
    virtual ~Node() = default;
};

对于你的程序,这将打印

创建节点
移动节点
复制节点

因为

std::vector<Node> vec;
vec.push_back(Node(1, 3));  // Creates a temporary node and moves it into the vector.

std::queue<Node> q;
q.push(vec[0]);             // Copys the node.

3
根据 文档,queue 中 push 函数的签名如下:
std::queue::push(const value_type& val)
std::queue::push(value_type&& val)

因此它基本上是被复制到队列中,可以通过以下方式进行验证:

#include <iostream>
#include <queue>

int main() {
    int iarray[3] = { 1,2,3 };
    std::queue<int> q;
    q.push(iarray[2]);
    std::cout << q.front();
    iarray[2] = 4;
    std::cout << q.front();
    return 0;
}

澄清一下,

发生的情况如下:

代码行q.push(iarray[2]);调用了队列类中的void push(const value_type& val)函数。

void push(const value_type& val)的定义为:

void push(const value_type& _Val) {
    c.push_back(_Val);
}

其中c_Container类的保护成员
代码行c.push_back(_Val)实际上调用了_Val对象的复制构造函数


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接