当前位置: 代码网 > 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)

相关文章:

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

发表评论

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