You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows环境下使用{}而非()初始化导致源码静默运行失败的原因解析

成员初始化列表中()与{}初始化的差异导致运行时崩溃问题

我花了大半天定位到一行导致程序静默崩溃的代码,仅有的差异是成员初始化的写法:

: data(std::move(value)) // 此写法正常运行
: data{std::move(value)} // 此写法导致运行时静默失败

我在MinGW 7(官网下载)和新安装的MinGW64 12.2.0版本中都测试过这段代码,问题复现一致。

问题核心

为什么这两种初始化写法会导致完全不同的运行结果?我原本以为在这个场景下()和{}初始化是等价的。这是编译器bug吗?它似乎横跨了GCC的多个主要版本。

环境说明

  • 编译标准:C++17(通过CMake设置)
  • 运行环境:Windows 10

源码

头文件 typeerased_original.h

#include <iostream>
#include <memory>
#include <string>
#include <vector>

template<typename T>
void draw(const T& t, std::ostream& os, std::size_t position)
{
    os << std::string(position, ' ') << t << std::endl;
}

class Object
{
public:
    template<typename T>
    Object(T value)
        : p_data(new Model<T>(std::move(value)))
    {
    }

    Object(const Object& object)
        : p_data(object.p_data->copy())
    {
    }

    Object(Object&& other) noexcept = default;

    Object& operator=(const Object& object)
    {
        Object tmp(object);
        *this = std::move(tmp);
        return *this;
    }

    Object& operator=(Object&& object) noexcept = default;

    friend void draw(const Object& object, std::ostream& os, std::size_t position)
    {
        object.p_data->draw_internal(os, position);
    }

private:
    struct Concept
    {
        virtual ~Concept() = default;
        virtual Concept* copy() const = 0;
        virtual void draw_internal(std::ostream&, std::size_t) const = 0;
    };

    template<typename T>
    struct Model : Concept
    {
        Model(T value)
            : data(std::move(value))       // <<<< 此写法正常运行
            //: data{std::move(value)}     // <<<< 此写法引发问题
        { }

        Concept* copy() const override
        {
            return new Model(*this);
        }

        void draw_internal(std::ostream& os, std::size_t position) const override
        {
            draw(data, os, position);
        }

        T data;
    };
    
    std::unique_ptr<Concept> p_data;
};

using Document = std::vector<Object>;

void draw(const Document& document, std::ostream& os, std::size_t position)
{
    os << std::string(position, ' ') << "<document>" << std::endl;
    for(const auto & item : document)
    {
        draw(item, os, position + 4);
    }
    os << std::string(position, ' ') << "</document>" << std::endl;
}

主文件

#include "typeerased_original.h"

int main()
{
    std::cout << "start of main" << std::endl;

    Document document;

    document.push_back(0);
    document.push_back(std::string("Hello World"));
    std::cout << "push back document" << std::endl;
    document.push_back(document);
    std::cout << "done" << std::endl; // 程序在此行输出前崩溃

    std::cout << "Drawing now!" << std::endl;
    draw(document, std::cout, 0);
    std::cout << std::endl;

    return 0;
}

原因分析

这不是编译器bug,而是C++列表初始化规则与代码中隐式转换结合导致的问题:

  1. 列表初始化的优先级规则:当使用{}进行初始化时,编译器会优先匹配类的initializer_list构造函数。对于Document(即std::vector<Object>)来说,它的initializer_list构造函数接受std::initializer_list<Object>。

  2. 隐式转换的触发:Object类有一个非explicit的模板构造函数template<typename T> Object(T value),这意味着任何类型(包括std::vector<Object>)都可以隐式转换为Object。当你用data{std::move(value)}初始化时,std::move(value)是std::vector<Object>&&类型,编译器会将其隐式转换为Object,然后用这个Object作为initializer_list<Object>的单个元素,构造出一个仅包含一个元素的std::vector<Object>,而非预期的移动构造原有的document。

  3. 崩溃的直接原因:当这个构造出的Object被push_back到原document中时,vector扩容需要拷贝所有元素。拷贝这个特殊的Object时,它内部的data是一个包含自身引用的vector<Object>,会触发无限递归拷贝,最终导致栈溢出、程序崩溃。

而使用data(std::move(value))时,编译器会直接匹配std::vector的移动构造函数,data会正确成为原document的移动版本,不会触发隐式转换和后续的无限递归。

解决方案

  • 将Object的模板构造函数声明为explicit,阻止隐式转换:
    template<typename T>
    explicit Object(T value)
        : p_data(new Model<T>(std::move(value)))
    {
    }
    
    此时data{std::move(value)}会正确调用移动构造函数,因为无法再将std::vector<Object>隐式转换为Object,initializer_list构造函数不再匹配。
  • 继续使用()进行成员初始化,避免触发initializer_list构造函数的匹配逻辑。

内容的提问来源于stack exchange,提问作者user2138149

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 13:42:22