探讨C++ std::queue队列涉及的拷贝问题

文章通过四个测试代码展示了C++中使用队列处理结构体时,拷贝构造函数和析构函数的调用情况。在向队列push元素时,会触发拷贝构造函数,pop时则调用析构函数。不同的对象生命周期和变量创建位置会影响拷贝和析构的次数,从而影响效率。测试表明,在pop时新建变量可能会导致额外的构造和析构操作,可能影响性能。
摘要由CSDN通过智能技术生成

想使用队列处理一些多线程变量传递问题, 过程中发现有些写法是可以优化的,这里记录一下队列push,pop过程中的拷贝问题

测试代码一如下:

#include <iostream>
#include <queue>

struct Job {
    int a{};
    float *b = new float;
    std::string c;

    Job() { printf("无参构造函数\n"); }

    Job(const Job &job) {
        printf("有参构造函数\n");
        this->a = job.a;
        this->b = job.b;
    };

    ~Job() { printf("析构函数\n"); }
};

std::queue<Job> qJobs;

int main() {
    Job job;
//    printf("%p\n",&job);
    printf("==========================\n");
    for (int i = 0; i < 2; ++i) {
//        Job job;
        printf("%p\n", &job);
        job.a = i + 1;
        job.b = (float *) malloc(sizeof(float) * 1);
        job.b[0] = float(i + 10);
//        qJobs.emplace(std::move(job));
        qJobs.push(job);
    }
    printf("--------------------------\n");
    Job job1;
    printf("job1变量地址: %p\n", &job1);
    while (!qJobs.empty()) {
        job1 = qJobs.front();
        qJobs.pop();
        printf("%p, %d, %f\n", &job1, job1.a, *job1.b);
    }
    printf("++++++++++++++++++++++++++++\n");
}

上述代码中, 向队列写入结构体元素, 之后再取出来打印,结果如下:

无参构造函数
==========================
0x7ffef349c110
有参构造函数
0x7ffef349c110
有参构造函数
--------------------------
无参构造函数
job1变量地址: 0x7ffef349c140
析构函数
0x7ffef349c140, 1, 10.000000
析构函数
0x7ffef349c140, 2, 11.000000
++++++++++++++++++++++++++++
析构函数
析构函数

进程已结束,退出代码0

可知队列的push操作会调用结构体或类的有参拷贝构造函数. 而每pop一个元素,也会调用该元素的析构函数.
而打印出来的job 和 job1 地址不同, 是因这个结构体变量都开辟在栈上, 创建job1时, job还没析构,所以会另开辟一块空间.
从最后打印结果也可知, job,job1两个变量是在程序结束时析构的. 测试代码二给出这点证明.

测试代码二如下:

int main() {
    { // 加作用域
        Job job;
//    printf("%p\n",&job);
        printf("==========================\n");
        for (int i = 0; i < 2; ++i) {
//        Job job;
            printf("%p\n", &job);
            job.a = i + 1;
            job.b = (float *) malloc(sizeof(float) * 1);
            job.b[0] = float(i + 10);
//        qJobs.emplace(std::move(job));
            qJobs.push(job);
        }
    }
    printf("--------------------------\n");
    Job job1;
    printf("job1变量地址: %p\n", &job1);
    while (!qJobs.empty()) {
        job1 = qJobs.front();
        qJobs.pop();
        printf("%p, %d, %f\n", &job1, job1.a, *job1.b);
    }
    printf("++++++++++++++++++++++++++++\n");
}
无参构造函数
==========================
0x7ffd0d5c6190
有参构造函数
0x7ffd0d5c6190
有参构造函数
析构函数
--------------------------
无参构造函数
job1变量地址: 0x7ffd0d5c6190
析构函数
0x7ffd0d5c6190, 1, 10.000000
析构函数
0x7ffd0d5c6190, 2, 11.000000
++++++++++++++++++++++++++++
析构函数

进程已结束,退出代码0

在上述测试二中,把job的生命周期限制在一个作用域中,在作用域结束后变量job释放空间, 那么创建job1会在已释放空间上创建, 注意测试一与测试二中地址的变化.

测试代码三如下:

那么,如果把job对象创建在for循环中,又会有什么不同呢?

int main() {
//    Job job;
//    printf("%p\n",&job);
    printf("==========================\n");
    for (int i = 0; i < 2; ++i) {
        Job job;
        printf("%p\n", &job);
        job.a = i + 1;
        job.b = (float *) malloc(sizeof(float) * 1);
        job.b[0] = float(i + 10);
//        qJobs.emplace(std::move(job));
        qJobs.push(job);
    }
    printf("--------------------------\n");
    Job job1;
    printf("job1变量地址: %p\n", &job1);
    while (!qJobs.empty()) {
        job1 = qJobs.front();
        qJobs.pop();
        printf("%p, %d, %f\n", &job1, job1.a, *job1.b);
    }
    printf("++++++++++++++++++++++++++++\n");
}
==========================
无参构造函数
0x7fff484f3740
有参构造函数
析构函数
无参构造函数
0x7fff484f3740
有参构造函数
析构函数
--------------------------
无参构造函数
job1变量地址: 0x7fff484f3740
析构函数
0x7fff484f3740, 1, 10.000000
析构函数
0x7fff484f3740, 2, 11.000000
++++++++++++++++++++++++++++
析构函数

进程已结束,退出代码0

结果表明, job每次都会创建新的, 拷贝到队列时会调用拷贝构造函数, 一次for循环结束时会析构掉. 每次for循环都是如此, 认为这样的效率比较低下. 同时, job的生命周期只在for中,因此pop时的job1 变量地址会与job相同.

测试代码四如下:

取出元素pop操作时, 每次新建变量效率如何呢?

int main() {
//    Job job;
//    printf("%p\n",&job);
    printf("==========================\n");
    for (int i = 0; i < 2; ++i) {
        Job job;
        printf("%p\n", &job);
        job.a = i + 1;
        job.b = (float *) malloc(sizeof(float) * 1);
        job.b[0] = float(i + 10);
//        qJobs.emplace(std::move(job));
        qJobs.push(job);
    }
    printf("--------------------------\n");
//    Job job1;
//    printf("job1变量地址: %p\n", &job1);
    while (!qJobs.empty()) {
        Job job1 = qJobs.front();
        qJobs.pop();
        printf("%p, %d, %f\n", &job1, job1.a, *job1.b);
    }
    printf("++++++++++++++++++++++++++++\n");
}
==========================
无参构造函数
0x7ffd01ed4a10
有参构造函数
析构函数
无参构造函数
0x7ffd01ed4a10
有参构造函数
析构函数
--------------------------
有参构造函数
析构函数
0x7ffd01ed4a10, 1, 10.000000
析构函数
有参构造函数
析构函数
0x7ffd01ed4a10, 2, 11.000000
析构函数
++++++++++++++++++++++++++++

进程已结束,退出代码0

可见,在pop时, 会在Job job1 = qJobs.front() 语句调用有参构造函数,之后再析构, 认为这也是一个效率低下的行为.

总结
个人喜欢测试代码一的构建方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值