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

C++中vector使用push_back(std::move)出现额外析构函数调用问题咨询

为什么使用std::move后析构函数调用次数远超预期?

我写了一段C++代码,原本预期析构函数只会被调用2次,但实际运行时却调用了4次,输出结果和预期不符,想知道哪里出错了。

我的代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
class my {
public:
    my() { counter++; std::cout << "class constructor" << counter << " \n";}
    ~my() { std::cout << "class destructor" << counter << " \n"; counter--; }
    static inline int counter = 0;
};
int main() {
    my v1;
    std::vector<my> my_vec;
    my * p = new my();
    my_vec.push_back(std::move(*p));
    my_vec.push_back(std::move(v1));
}

实际运行输出:

class constructor1
class constructor2
class destructor2
class destructor1
class destructor0
class destructor-1


问题根源分析

咱们一步步拆解代码里的对象生命周期和操作,就能理清为什么析构次数会超出预期:

1. 你忽略了vector扩容带来的析构

当你第二次调用push_back时,vector的初始容量(通常是1)不足以容纳第二个元素,会触发扩容操作:

  • 先分配一块更大的新内存;
  • 把vector里已有的那个对象移动到新内存;
  • 析构旧内存里的原对象——这就是你没预料到的第一次额外析构,对应输出里的class destructor2。

2. 编译器生成的默认移动构造没更新你的静态计数器

你的类没有自定义移动构造函数,编译器会生成一个默认版本。这个默认移动构造不会调用你的构造函数(也就不会执行counter++),但析构函数依然会执行counter--。这就导致你的counter数值和实际存在的对象数量完全不匹配,看起来像是析构次数“多了”,其实是计数器的统计逻辑本身有问题。

3. 堆对象没有被delete,存在内存泄漏

你用new创建了*p,但全程没调用delete p,这个堆对象的析构函数永远不会被调用——它的生命周期不会随main结束而自动结束,操作系统只会回收内存,但不会执行C++的析构逻辑。


修正后的代码示例

要解决这些问题,我们需要补全移动构造函数、预分配vector容量避免扩容,同时记得释放堆对象:

#include <iostream>
#include <vector>
#include <algorithm>
class my {
public:
    my() { counter++; std::cout << "default constructor, counter: " << counter << "\n"; }
    // 补全拷贝构造(如果需要的话)
    my(const my&) { counter++; std::cout << "copy constructor, counter: " << counter << "\n"; }
    // 自定义移动构造,明确计数器逻辑(这里因为类无资源,只是做统计)
    my(my&&) noexcept { counter++; std::cout << "move constructor, counter: " << counter << "\n"; }
    ~my() { std::cout << "destructor, counter: " << counter << "\n"; counter--; }
    
    static inline int counter = 0;
};
int main() {
    my v1;
    std::vector<my> my_vec;
    // 预分配容量,避免扩容
    my_vec.reserve(2);
    
    my *p = new my();
    my_vec.push_back(std::move(*p));
    // 释放堆对象
    delete p;
    
    my_vec.push_back(std::move(v1));
}

这段代码运行时,你能清晰看到每个构造、移动、析构的调用过程,计数器也能准确反映对象数量的变化,同时不会有额外的扩容析构和内存泄漏。

内容的提问来源于stack exchange,提问作者GregK1

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 18:07:50