是否应提供std::initializer_list构造函数重载的技术探讨
上下文代码示例
#include <vector> class DatasetValue { public: using ValueType = int; using Values = std::vector<ValueType>; DatasetValue(Values values) : m_values{std::move(values)} { } const Values &values() const { return m_values; } private: Values m_values; }; int main() { using Dataset = std::vector<DatasetValue>; auto dataset = Dataset{ {{1, 2, 3, 4, 5}}, {{7, 6, 5, 4, 3, 2, 1}}, {{1, 2, 3, 4, 5, 6, 7, 8, 9}}, {{3, 2, 1}}, {{1}}, {{ 4, 3, 2, 1}} }; }
我们有一个Dataset对象,通常在应用启动时用预定义值初始化,运行正常。但将构造函数参数改为std::initializer_list<ValueType>后,在ARM Cortex M4环境下生成的二进制大小减少了约1KB,修改后的构造函数如下:
DatasetValue(std::initializer_list<ValueType> values) : m_values{values} {}
问题
是否应尝试提供接受std::initializer_list的构造函数重载,以替代或根据场景配合容器类型的左值/右值重载?
补充说明
尚未测试该方案对list、map等其他容器是否同样有益,希望了解相关见解;测试是在无优化开启的情况下完成的。
回答
核心原因分析
原构造函数接受Values(即std::vector<ValueType>)参数,当用字面量列表(如{{1,2,3}})初始化时,编译器会先创建一个临时vector,再将其移动构造给函数参数,最后移动到成员变量m_values。而使用std::initializer_list的构造函数则直接用列表初始化成员变量m_values,完全跳过了临时vector的创建、移动相关代码——在无优化的编译环境下,这些冗余代码无法被消除,直接导致二进制体积增大。
是否应该提供重载?
优先添加
std::initializer_list重载
如果你的主要初始化场景是示例中的预定义字面量列表,添加该重载非常值得:它不仅能在无优化环境下显著减小二进制体积,即使开启优化,也能避免编译器可能无法完全消除的临时对象开销,让初始化逻辑更直接。保留原有容器参数的构造函数
对于需要直接传递已有vector(左值或右值)的场景,原构造函数依然不可替代:直接传递已有容器时,原构造函数可以通过移动语义避免拷贝,效率远高于通过initializer_list重新构造vector。因此建议同时保留两种构造方式,根据初始化场景自动匹配。
对其他容器的适用性
std::list:逻辑和vector一致。使用std::initializer_list构造可以避免临时list的创建、移动代码,在无优化环境下同样能减小二进制体积。虽然list的节点分配特性导致运行时行为和vector不同,但二进制优化的核心逻辑是通用的。std::map:如果用键值对列表初始化,std::initializer_list<std::pair<K,V>>的构造方式同样能跳过临时map的创建步骤,从而减少二进制体积。尽管map的构造逻辑更复杂,但核心原理和序列容器一致。
关于无优化测试的注意事项
开启优化后,编译器可能会将临时容器的构造、移动代码优化掉,此时两种构造方式的二进制差异会缩小。但在嵌入式环境中,有时会因调试需求或编译限制关闭部分优化,因此std::initializer_list的构造方式依然具备实际的优化价值。
内容的提问来源于stack exchange,提问作者SVcpp

