【C++】构造函数和初始化列表的性能差距

构造函数和初始化列表的性能差距对比测试

1.说明

在C++类和对象中,你可能听到过更加推荐用初始化列表来初始化类内成员。如果类内成员是自定义类型,则只能在初始化列表中调用自定义类型的构造函数。

但初始化列表和在构造函数体内直接赋值有无性能差距呢?今天就用一份相对简单的代码来测试一下

2.测试

2.1 代码说明

首先是一个自定义类型,实现带缺省值的默认构造,拷贝构造和赋值重载这三个函数,并在内部新增打印来区分不同的函数

cpp 复制代码
struct mytest {
public:
    mytest(int a = -1)
    {
        _a = a;
        cout << "mytest() " << a << endl;
    }
    mytest(const mytest& st) {
        _a = st._a;
        cout << "mytest(copy) " << st._a << endl;
    }
    mytest& operator=(const mytest& st)
    {
        _a = st._a;
        cout << "mytest operator= " << st._a << endl;
        return *this;
    }
private:
    int _a;
};

再在另外一个类中使用这个自定义类型

cpp 复制代码
struct myclass {
public:
    myclass(const struct mytest& st, int b)
    {
        cout << "myclass() _b:" << _b << endl;
    }
    struct mytest _sa;
    int _b;
};

此时构造函数就有两种写法,一种是在初始化列表中初始化这个自定义类型

cpp 复制代码
	myclass(const struct mytest& st, int b)
        :_sa(st),
        _b(b)
    {
        cout << "myclass() _b:" << _b << endl;
    }

另外一种是在构造函数体内通过赋值重载来初始化这个自定义类型

cpp 复制代码
    myclass(const struct mytest& st, int b)
    {
         _sa = st;
         _b = b;
        cout << "myclass() _b:" << _b << endl;
    }

需要注意的是,这里的自定义类型传参采用了引用传参,并不会因此产生额外的拷贝!

主函数体如下,为了区分第一个mytest的构造函数,我在其后新增了一行输出作为分割

cpp 复制代码
int main()
{
    mytest test_a(1);
    cout << "------" << endl;
    myclass test(test_a, 3);
    return 0;
}

2.2 测试

先来看看使用赋值初始化的方式,可以看到,虽然我们在初始化列表中什么都没有写,但是这里依旧调用了默认的构造函数 (因为默认构造函数的缺省值给的是-1,这里能通过参数判断出来这并不是我们显式调用的构造)

调用了默认构造后,又通过赋值重载来初始化了一遍_sa,相当于两次初始化

但如果调用初始化列表,则只会有一次拷贝构造,避免了额外的默认构造调用!

在linux下也测试过了,结果和VS2019相同

3.结论

结论就出来了:初始化列表能节省一次默认构造的调用,优化性能!

3.1 实际场景

在上面的场景中,性能差距可能并不会特别大,但是在下面的场景中可能就不一样了

cpp 复制代码
struct mytest {
public:
    mytest(int sz = 1)
    {
        _str = new char[sz];
        _sz = sz;
        cout << "mytest() " << sz << endl;
    }
    mytest(const mytest& st) {
        delete _str;// 需要先销毁原视的数据
        _str = new char[st._sz]; // 再创建一个新的
        _sz = st._sz;
        //省略拷贝数据的代码
        cout << "mytest(copy) " << st._sz << endl;
    }
    mytest& operator=(const mytest& st)
    {
        delete _str;// 需要先销毁原视的数据
        _str = new char[st._sz]; // 再创建一个新的
        //省略拷贝数据的代码
        cout << "mytest& operator= " << st._sz << endl;
        return *this;
    }
private:
    char* _str;
    size_t _sz;
};

struct myclass {
public:
    myclass(const struct mytest& st, int b)
        :_sa(st),
        _b(b)
    {
         //_sa = st;
         //_b = b;
        cout << "myclass() _b:" << _b << endl;
    }
    struct mytest _sa;
    int _b;
};

在这个场景中,因为mytest自定义类型的拷贝构造涉及到了深拷贝,此时就需要将已有的空间给销毁了,再new一片新的空间出来,再将数据给拷贝过去。

白白多了一层默认构造中的new空间的+拷贝构造中delete原有空间的消耗!

如果类中需要深拷贝的成员不止一个,那性能差距就更大!

所以在C++中,一律以初始化列表优先!


这里顺带提一嘴初始化列表的小坑,也算是复习;

当你使用初始化列表来初始化类内成员的时候,初始化的顺序是类内成员声明的顺序,而不是初始化列表中的顺序!这点非常重要,如果顺序不对,可能会出现使用未定义(还没有初始化完成)的参数的bug!

相关推荐
DeepAgent3 小时前
AI Agent 工程实践(49):一次真实优化——从 Agent v1 到 v2
开发语言·人工智能·agent
知识分享小能手3 小时前
C++ 学习教程,从入门到精通,C++对象和类 — 详细知识点总结(10)
开发语言·c++·学习
顺顺 尼4 小时前
linux 进程信号(上)
linux·c++·算法
郑州光合科技余经理4 小时前
同城电商系统:库存变更怎么同步到订单
java·开发语言·前端·后端·uni-app·php·ai编程
思茂信息5 小时前
CST软件BCI仿真模型及仿真案例
开发语言·单片机·嵌入式硬件·算法·emc
海上小飞龙5 小时前
改一个数,右边全得重算,这题怎么扛住两万次查询
java·c++·python
阿里嘎多学长5 小时前
2026-09-28 GitHub 热点项目精选
开发语言·程序员·github·代码托管
Persistent的粽子!5 小时前
双指针算法:最大盛水容器
c++·算法·leetcode
小小龙学IT5 小时前
三菱 PLC MC 协议(SLMP / QnA 兼容 3E 帧)深度解析:从帧结构到 C++/Go 双语言采集实战
c语言·c++·golang
程序猿编码6 小时前
C++/CUDA 手写 LLM 推理引擎:拆解 vLLM 核心 PagedAttention 与连续批处理
开发语言·c++·深度学习·神经网络·推理·vllm