【问题标题】:C++ Boost Priority Queue BehaviorC++ 提升优先级队列行为
【发布时间】:2022-01-18 00:01:56
【问题描述】:

我试图弄清楚提升优先级队列是如何实现的,但我很困惑。

头文件(main.hpp):

#ifndef MAIN_HPP
#define MAIN_HPP

#include <cstddef>
#include <cstdint>
#include <iostream>

#include <boost/heap/priority_queue.hpp>

typedef std::int32_t int32_t;

typedef struct st {
    int32_t num;
    int32_t f;

    st() {
        std::cout << "DEFAULT" << std::endl;
        this->f = 0;
    }

    st(int32_t num) {
        std::cout << "creation\t" << num << std::endl;
        this->num = num;
        this->f = 0;
    }

    ~st() {
        std::cout << "del\t" << num << std::endl;
        f = 1;
    }
} st;

typedef struct st_c0 {
    bool operator()(const st& st0, const st& st1) const {
        return (st0.num > st1.num);
    }
} st_c0;

typedef struct st_c1 {
    bool operator()(const st* st0, const st* st1) const {
        return (st0->num > st1->num);
    }
} st_c1;

#endif
#include "main.hpp"

int main() {
    boost::heap::priority_queue<st, boost::heap::compare<st_c0>> q0;
    boost::heap::priority_queue<st*, boost::heap::compare<st_c1>> q1;
    st y = st(5);
    q0.push(st(44));
    q0.push(y);
    q0.empty();
    std::cout << y.f << std::endl;
    return 0;
}

我得到的输出是:

creation        5
creation        44
del     44
del     44
del     44
del     44
del     44
del     5
del     5
del     5
0
del     5
del     5
del     44

对象创建和删除的顺序没有意义。优先级队列的内部是如何工作的,它们的最佳实践是什么(存储指针与存储对象)?

【问题讨论】:

    标签: c++ memory boost priority-queue heap


    【解决方案1】:

    您没有考虑为您的班级自动创建的copy-constructor

    在您的情况下,您不会自动获得移动构造函数,但很高兴看到编译器可以在哪里进行移动而不是复制。

    如果您将st 更改为例如:

    
    struct st {
        int32_t num;
        int32_t f;
    
        st() {
            std::cout << this << "\tctor default" << std::endl;
            this->f = 0;
        }
    
        st(int32_t num) : num(num), f(0) {
            std::cout << this << "\tctor num\t" << num << std::endl;
        }
    
        st(st const& other) : num(other.num), f(other.f) {
            std::cout << this << "\tctor copy\t" << num << "\t (from " << &other << ")" << std::endl; 
        }
    
        st(st&& other): num(other.num), f(other.f) {
            std::cout << this << "\tctor move\t" << num << "\t (from " << &other << ")" << std::endl;
        }
    
        st& operator=(st const& other) {
            num = other.num;
            f = other.f;
            std::cout << this << "\tassign copy\t" << num << "\t (from " << &other << ")" << std::endl;
            return *this;
        }
    
        st& operator=(st&& other) {
            num = other.num;
            f = other.f;
            std::cout << this << "\tassign move\t" << num << "\t (from " << &other << ")" << std::endl;
            return *this;
        }
    
        ~st() {
            std::cout << this << "\tdtor\t\t" << num << std::endl;
        }
    };
    

    godbolt example

    您会更好地了解正在发生的事情:

    // construct y
    0x7fffd8f3b1e8  ctor num    5
    // call to push(st(44))
    0x7fffd8f3b238  ctor num    44
    0x7fffd8f3b1b4  ctor copy   44   (from 0x7fffd8f3b238)
    0x97cec0        ctor move   44   (from 0x7fffd8f3b1b4)
    0x7fffd8f3b1b4  dtor        44
    0x7fffd8f3b164  ctor move   44   (from 0x97cec0)
    0x7fffd8f3b178  ctor move   44   (from 0x7fffd8f3b164)
    0x97cec0        assign move 44   (from 0x7fffd8f3b178)
    0x7fffd8f3b178  dtor        44
    0x7fffd8f3b164  dtor        44
    0x7fffd8f3b238  dtor        44
    // call to push(y)
    0x7fffd8f3b1b4  ctor copy   5    (from 0x7fffd8f3b1e8)
    0x97cee8        ctor move   5    (from 0x7fffd8f3b1b4)
    0x97cee0        ctor copy   44   (from 0x97cec0)
    0x97cec0        dtor        44
    0x7fffd8f3b1b4  dtor        5
    0x7fffd8f3b164  ctor move   5    (from 0x97cee8)
    0x7fffd8f3b178  ctor move   5    (from 0x7fffd8f3b164)
    0x97cee8        assign move 44   (from 0x97cee0)
    0x97cee0        assign move 5    (from 0x7fffd8f3b178)
    0x7fffd8f3b178  dtor        5
    0x7fffd8f3b164  dtor        5
    // after main()
    0x7fffd8f3b1e8  dtor        5
    0x97cee0        dtor        5
    0x97cee8        dtor        44
    

    所以分解一下:

      1. 推入第一个元素
      • 您的st 已构建,然后复制和移动了几次。
      • 最终以0x97cec0(堆中分配的存储)结束
      1. 推第二个元素
      • 第二次调用触发了调整大小,因此必须将 44 移动到新的分配中
      • 5 也被复制和移动了一点
      • 5 和 44 已交换到位,因此优先级队列已正确排序
      1. empty()
      • 什么都不做(会返回 true,因为容器包含元素)
      • 如果要删除所有元素,请使用clear()
      1. 主要返回后
      • y 被破坏
      • 优先级队列被破坏并为st 调用析构函数

    虽然boost::heap::priority_queue&lt;st, boost::heap::compare&lt;st_c0&gt;&gt; 的实现有多少副本/移动没有保证,所以这可能随时改变。


    指针与对象

    一般来说,只要对象很小且易于复制/移动,您就会使用它们。

    如果你使用指针,你的对象根本不会被复制或移动,只有指向它的指针,所以如果你的对象很大和/或复制/移动成本很高,这会更好。

    但是对于裸指针,您还需要手动 delete 它们,如果可能的话,我建议使用智能指针,例如:

    boost::heap::priority_queue<boost::shared_ptr<st>, boost::heap::compare<TODO>> q0;
    

    这样你的 ``st`'s 会自动被释放并且你不必手动删除它们。

    【讨论】:

    • 是的,我正在做的事情有相当大的对象,所以我认为最安全的做法是专门为队列创建包装类,这样我就不必处理 NULL 检查
    • 我从来没有掌握跨多个函数的智能指针和所有权,所以这是我必须研究的问题。
    • @dancxviii 我肯定会推荐智能指针,它们易于使用,并且可以轻松编写正确的程序 :) unique_ptr 非常简单 - 如果 unique_ptr 破坏它也会破坏你的对象并释放内存。对于shared_ptr,您可以有多个指向同一个对象 - 一旦您的对象的最后一个 shared_ptr 消失,您的对象将被破坏。 (所以shared_ptr 基本上是一个带有引用计数器的普通指针,当计数达到 0 时删除指针)。
    猜你喜欢
    • 1970-01-01
    • 2016-04-13
    • 1970-01-01
    • 2019-10-14
    • 1970-01-01
    • 2017-06-13
    • 1970-01-01
    • 1970-01-01
    • 2019-04-25
    相关资源
    最近更新 更多