You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++优先队列中自定义比较器类的复制构造函数过度调用问题

自定义比较器的priority_queue频繁触发复制构造函数的原因分析

问题场景与代码

我声明了一个带有包含vector属性的自定义比较器的priority_queue,完整代码如下:

#include <bits/stdc++.h>

using namespace std;

class Compare
{
private:
    vector<int> vec;

public:
    Compare(const vector<int> &vec) { this->vec = vec; }

    Compare(const Compare &obj)
    {
        this->vec = obj.vec;
        cout << "Copy Constructor Called!\n";
    }

    bool operator()(const int &left, const int &right)
    {
        return vec[left] > vec[right];
    }
};

int main(void)
{
    vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    priority_queue<int, vector<int>, Compare> pq{Compare(vec)};

    cout << "Pushing 1\n";
    pq.push(1);
    cout << "Pushed 1\n";
    cout << "Pushing 2\n";
    pq.push(2);
    cout << "Pushed 2\n";

    while (!pq.empty())
    {
        cout << "Popped = " << pq.top() << '\n';
        pq.pop();
    }
    return 0;
}

异常输出结果

Copy Constructor Called!
Copy Constructor Called!
Copy Constructor Called!
Copy Constructor Called!
Pushing 1
Copy Constructor Called!
Copy Constructor Called!
Copy Constructor Called!
Pushed 1
Pushing 2
Copy Constructor Called!
Copy Constructor Called!
Copy Constructor Called!
Pushed 2
Popped = 1
Copy Constructor Called!
Copy Constructor Called!
Copy Constructor Called!
Copy Constructor Called!
Copy Constructor Called!
Popped = 2
Copy Constructor Called!

问题疑问

为何复制构造函数会被如此频繁调用,甚至每次push、pop操作都会触发?此外,仅声明priority_queue而不执行push或pop时,复制构造函数也被调用了4次。这是为什么?priority_queue不是应该存储比较器对象并在需要比较时才使用吗?

原因分析与解决方案

1. 初始化阶段的多次复制

当你用临时对象Compare(vec)初始化priority_queue时,STL容器的实现会多次拷贝这个比较器:

  • 首先,临时对象会被复制到priority_queue内部存储的比较器实例中;
  • 部分STL实现(比如GCC的libstdc++)在初始化底层vector容器时,内部辅助逻辑会额外触发几次比较器的拷贝,这是实现层面的细节。

2. push/pop操作触发复制的根源

priority_queue的push和pop需要维护堆结构(上浮、下沉),这些操作依赖比较器的operator()。你的STL实现可能每次调用比较器时都会拷贝一份对象,而非直接使用容器内存储的实例。这种设计早期是为了避免线程安全问题或简化逻辑,并非所有STL实现都会这样,但你的环境恰好采用了这种方式。

3. 优化方案:减少复制次数

  • 让比较器持有vector引用:避免每次复制比较器时拷贝整个vector,同时要保证原vector的生命周期长于priority_queue;
  • 添加移动构造函数:允许比较器被移动而非复制,降低开销;
  • 用std::ref传递比较器:让priority_queue存储比较器的引用,彻底避免复制。

修改后的示例代码:

#include <bits/stdc++.h>

using namespace std;

class Compare
{
private:
    const vector<int>& vec; // 改为引用,避免vector拷贝

public:
    // 用初始化列表构造
    Compare(const vector<int>& vec) : vec(vec) {}

    // 提供移动构造函数
    Compare(Compare&& obj) noexcept : vec(obj.vec) {
        cout << "Move Constructor Called!\n";
    }

    // 禁用复制构造(引用类型的复制可能引发悬空引用)
    Compare(const Compare&) = delete;

    // operator()标记为const,符合STL对比较器的要求
    bool operator()(const int& left, const int& right) const
    {
        return vec[left] > vec[right];
    }
};

int main(void)
{
    vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    // 用std::ref传递比较器,避免复制
    priority_queue<int, vector<int>, Compare> pq{std::ref(Compare(vec))};

    cout << "Pushing 1\n";
    pq.push(1);
    cout << "Pushed 1\n";
    cout << "Pushing 2\n";
    pq.push(2);
    cout << "Pushed 2\n";

    while (!pq.empty())
    {
        cout << "Popped = " << pq.top() << '\n';
        pq.pop();
    }
    return 0;
}

内容的提问来源于stack exchange,提问作者Neeraj-Kumar-Coder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 12:22:06