"Incomplete" 对象实例化和输出行为

"Incomplete" object instantiation and output behavior

下面的代码是如何工作的?

#include <cstdio>

template<class T>
T x = T{};

void foo()
{
    class Test
    {
    public:
        Test() { std::printf("Test::Test\n"); }
    };

    Test t = x<Test>;
}


int main()
{
    std::printf("main\n");
}

输出

Test::Test
main

Live example

此外,如果我将 std::printf 调用更改为 std::cout,整个程序就会崩溃:

#include <iostream>

template<class T>
T x = T{};

void foo()
{
    class Test
    {
    public:
        Test() { std::cout << "Test::Test\n"; }
    };

    Test t = x<Test>;
}


int main()
{
    std::cout << "main\n";
}

输出

Segmentation fault      (core dumped) ./a.out

Live example

为什么?

正如其他人已经提到的,您使用了变量模板。

如果我没记错的话,变量模板类似于这样的东西:

template<class T>
struct X {
    static T x;
};

template<class T>
T X<T>::x = T{};

然后你使用它,就像这样:

void foo() {
    class Test {
    public:
        Test() { std::printf("Test::Test\n"); }
    };

    Test t = X<Test>::x;
}

如果您尝试这样做,您将看到相同的结果:coliru

模板在foo中实例化,并发出初始化静态成员的代码。 此初始化发生在 main 运行之前,因此您会看到 Test::Test 首先打印出来。

至于初始化发生,尽管使用变量的代码从未被调用 - 我 假设 编译器可以尝试推理 foo 从未被调用整个程序,Test 是一个本地 class,其类型不会转义 foo,因此使实例化 X<Test>::x 对其他任何人都无法访问,并决定将其删除...

...但我想这在 link 时需要一些努力,而且我没有看到标准强制要求这种行为。

此外,我不确定 compiler/linker 是否允许删除非局部变量的初始化,如果该初始化有副作用。