当前位置: 代码网 > it编程>编程语言>C/C++ > C++中使用大括号初始化时需要注意哪些问题?一文总结

C++中使用大括号初始化时需要注意哪些问题?一文总结

2026年09月13日 • C/C++ •我要评论
一、列表初始化和初始化列表对于使用大括号{}进行初始化,在前面反复分析过,给出的建议是可能的情况下尽量使用大括号进行初始化。但这并不意味着大括号是完美的初始化方式。这里要显式的说明一下大括号{}和st

一、列表初始化和初始化列表

对于使用大括号{}进行初始化,在前面反复分析过,给出的建议是可能的情况下尽量使用大括号进行初始化。但这并不意味着大括号是完美的初始化方式。

这里要显式的说明一下大括号{}和std::initializer_list两种机制。从形式让讲前面的大括号叫列表初始化,而后面的std::initializer_list可以叫做初始化列表。而本质上,std::initializer_list作为c++11引入的轻量级的模板类,专门用于支持前者列表初始化。

这是一件好事,但可能在某些情况下,好事也会变成不好的事。注意,这里没说是坏事。

这里需要注意std::initializer_list的几个特点即只读性(内部元素视为const)、不支持移动语义、浅拷贝以及类型统一要求。看看这几点,哪一点单独拿出来都是在走钢丝。所以,它的问题,一定会在使用不当的时候暴露出来。

二、主要的问题

对于使用大括号进行初始化,除了前面提到的窄化转换问题,其它还有几个重要的问题需要说明。

主要包括:

1、initializer_list构造函数优先级过高

对于一些类中定义了支持std::initializer_list的构造函数时,编译器会优先选择更匹配std::initializer_list的构造函数,这是最容易产生意外行为的问题。

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v1(10, 9); // 10个元素,每个都是9
    std::vector<int> v2{10, 9}; // 两个元素:10和9

    std::cout << v1.size() << std::endl; // 10
    std::cout << v2.size() << std::endl; // 2
}

如果一个类同时有普通构造函数和 initializer_list构造函数,使用 {}时会优先尝试后者:

#include <initializer_list>
#include <iostream>

class demo {
public:
    demo(int count, int value)
    {
        std::cout << "普通构造函数"<<std::endl;
    }

    demo(std::initializer_list<int>)
    {
        std::cout << "initializer_list构造函数"<<std::endl;
    }
};

int main()
{
    demo a(10, 9); // 普通构造函数
    demo b{10, 9}; // initializer_list构造函数
}

遇到这一类问题,解决的首先是判定是否支持initializer_list,特别是对stl的库容器等,要严格小心注意。也可以从应用层出发,对于参数设置,则直接调用构造函数即使用小括号的机制;而对于一组元素的初始化时,则使用大括号。另外,如果是自写编写相关类,没有显式的要求下,不要提供initializer_list的构造函数。

2、initializer_list元素只读,不能拷贝

在前面的注意点中提到了std::initializer_list 的元素类型相当于 const t。

所以在一些需要移动结合应用的情况下,代码就会出现问题:

#include <memory>
#include <vector>

std::vector<std::unique_ptr<int>> data{
    std::make_unique<int>(1),
    std::make_unique<int>(2)
};

大家都知道,unique_ptr只可移动不可复制,而列表中的元素是 const,意味着无法进行移动。

要想实现这个效果则可以使用:

std::vector<std::unique_ptr<int>> data;
data.reserve(2);

data.push_back(std::make_unique<int>(1));
data.push_back(std::make_unique<int>(2));

当然,这里更建议使用 emplace_back,但资源所有权场景通常优先使用 make_unique。

3、initializer_list的生命周期问题

std::initializer_list可以看作一种底层数据的上层 的view,所以不要返回指向临时列表底层数组的 initializer_list。

这和不要返回临时变量的指针有些类似:

#include <initializer_list>

std::initializer_list<int> getdata()
{
    return {3, 6, 9}; // 危险:返回后底层数组已经失效
}

应返回拥有数据所有权的容器,其实就是进行了拷贝:

#include <array>

std::array<int, 3> getdata()
{
    return {3, 6, 9};
}

//或

#include <vector>

std::vector<int> getdata()
{
    return {3, 6, 9};
}

不过如果可以保障其安全的作用域,是可以正常使用的。比如作为函数参数临时使用通常是安全的:

void print(std::initializer_list<int> data);
print({3, 6, 9}); // 列表在整个函数调用期间有效

但是如果对列表的 begin()等指针进行保存并在函数调用结束后继续使用,仍然会出现问题。

4、模板参数的推导

在前面学习ctad时,模板可以自动进行模板参数的推导。

但如果是从单纯的{}往往推导无法成功,如下:

template<class t>
void set(const t& v){}

// set({3, 3, 3}); // 编译错误:无法推导 t

这时可以显式的构造对象类型,以供推导。如:

#include <vector>

set(std::vector<int>{3, 3, 3});

或者增加重载函数,使用std::initializer_list:

#include <initializer_list>

template<class t>
void set(std::initializer_list<t> v){}

int main()
{
    set({3, 3, 3}); // t 推导为 int
}

同样,下面的代码一般也无法自动推导:

std::make_unique<std::vector<int>>({3, 3, 3}); // 无法推导

和上面一样,或者使用initializer_list或者预先显式的构造容器:

auto p = std::make_unique<std::vector<int>>(
    std::initializer_list<int>{3, 3, 3}
);
auto p = std::make_unique<std::vector<int>>(
    std::vector<int>{3, 3, 3}
);

5、列表初始化的应用方式对explicit构造的影响

对于拥有显式的explicit构造函数的类,不能使用隐式的调用它。

这在大括号初始化中同样适用:

class demo {
public:
    explicit demo(int value) {}
};

demo d{10};      // ok:直接列表初始化
// demo d1 = {10}; // err:隐式调用 explicit构造函数

正如例程中一样,只能是显式的调用构造函数:

demo d{10};

在通过函数传递参数时,也应如此:

void set(demo d);
set(demo{10});

6、空列表可能导致歧义

看下面的代码:

void set(std::vector<int>);
void set(std::string);

int main()
{
    // set({}); // err:重载匹配错误
}

处理这种问题,只能是显式的给出调用参数的数据类型:

set(std::vector<int>{});
set(std::string{});

7、聚合类型初始化的顺序问题

在新的标准中提供了聚合类型的初始化,但顺序的依赖性也由此产生,不能跳着初始化或个别不初始化。如下:

struct data
{
    int id;
    char* name;//char name[100];
};
data d{31,"tom"};

如果换顺序则可能会出现各种问题。如果想忽略某个值的初始化,可使用c++20中的指定初始化,但其仍然需要严格按顺序进行。

8、对auto支持的问题

有个c++大牛强力推荐使用auto。但它在遇到初始化列表时,也要注意。

看下面的代码:

auto a{1};        // int
auto b = {2};     // std::initializer_list<int>
auto c = {3, 4};  // std::initializer_list<int>

// auto d{5, 6};     // err:直接列表初始化只能有一个元素
// auto e = {7, 8.0}; // err:元素类型不统一

c/c++天然对int支持的好啊。没办法,胎里带。处理这类问题,还是显式的给出类型。不再给出例子。

三、应用说明

在明白了上述大括号初始化的细节问题后,其实就也有一个清晰的正确使用的方式:

  • 基本类型和聚合类型初始化,推荐使用列表初始化
  • 要严格处理拥有initializer_list构造函数的类,特别注意参数设置和元素初始化。比如stl中的向量等
  • initializer_list不可应用于不可复制对象的批量初始化
  • 不可扩大initializer_list相关数据的应用范围
  • 要严格限制在template,auto,overload三种情况下的应用,显式明确相关对象的类型列表

总体上来说,使用大括号这种列表初始化的机制,统一、方便。仍然是对象初始化的优先使用方式,但在使用方便时,也要看到其中细节的问题,不能一头栽进去,出了问题还不知道为什么。

四、总结

在任何情况下都不要想当然,都要严格的按标准行事。扩大影响或使用范围,都需要当前环境或工具的显式支持。只有这样,才可能写出安全稳定的c++代码。

列表初始化作为一个被广泛推荐的初始化方式,仍然在c++中存在着这样那样的细节上的陷阱。所以,对于c++开发者来说,不能轻易的滥用任务技术。自己的大脑中始终要保持清醒和警惕!

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

赞 (0)

相关文章:

  • C++的类型转换详解

    一.c语言的类型转换在 c 语言里,当赋值运算符两边类型不一致、形参和实参类型不匹配、或者函数返回值与接收变量类型不一样时,就会触发类型转换。c 语言的类型转换分为两种:隐式类型转…

    2026年09月08日 • 编程语言
  • C语言中字符函数和字符串函数的使用与实现详解

    C语言中字符函数和字符串函数的使用与实现详解

    查询文档cplusplus.com 编码过程当中我们经常会遇到处理字符或者字符串的场景, 比如你现在看到的每一个字 字符和字符串字符用''引起(... [阅读全文]
  • C语言的递归与尾递归的实现

    C语言的递归与尾递归的实现

    什么递归c语言允许函数调用自己,这种调用过程就叫递归。如下面的函数up_and_down:#include <stdio.h>void up_and... [阅读全文]
  • c语言分支与循环语句案例详解

    c语言分支与循环语句案例详解

    程序并不是把语句从上到下机械地执行一遍。面对不同输入,它需要做出选择;遇到重复任务,它需要自动执行多次;在特殊情况下,还要提前结束当前操作。分支、循环和跳转语句... [阅读全文]
  • C语言实现结构体、联合和枚举

    一、结构体结构是一些值的集合,这些值称为成员变量,结构的每个成员都可以是不同类型的变量。1.结构体的声明struct tag{ member-list;}variable-l…

    2026年09月08日 • 编程语言
  • C语言中位运算的实现

    C语言中位运算的实现

    位运算的基本概念位运算是直接对整数在内存中的二进制位进行操作的一类运算。这类运算通常效率高,适用于底层编程、性能优化和特定算法场景。常见的位运算包括与(and)... [阅读全文]

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com