未使用的函数能否根据 C++14 实例化具有副作用的变量模板?
Can an unused function instantiate a variable template with side-effects according to C++14?
这是我的代码:
#include <iostream>
class MyBaseClass
{
public:
static int StaticInt;
};
int MyBaseClass::StaticInt = 0;
template <int N> class MyClassT : public MyBaseClass
{
public:
MyClassT()
{
StaticInt = N;
};
};
template <int N> static MyClassT<N> AnchorObjT = {};
class UserClass
{
friend void fn()
{
std::cout << "in fn()" << std::endl; //this never runs
(void)AnchorObjT<123>;
};
};
int main()
{
std::cout << MyBaseClass::StaticInt << std::endl;
return 0;
}
输出为:
123
...指示 MyClassT()
构造函数被调用,尽管 fn()
从未被调用。
在 gcc
和 clang
上测试 -O0
、-O3
、-Os
甚至 -Ofast
问题
根据 C++ 标准,此程序是否具有未定义的行为?
换句话说:如果更高版本的编译器设法检测到 fn()
永远不会被调用,他们可以优化模板实例化以及 运行 构造函数吗?
能否以某种方式使此代码具有确定性,即强制构造函数 运行 - 不 引用函数名称 fn
或模板参数值 123
在 UserClass
?
之外
更新: 版主 t运行 回答了我的问题并建议进一步 t运行cation。详细原版可以查看here.
"If later versions of compilers manage to detect that fn() will never be called [and] they optimize away template instantiation" 那么那些编译器就会被破坏。
C++ 编译器可以自由实现任何优化没有明显效果。在您概述的情况下,至少会有一个可观察到的效果:即静态 class 成员不会被构造和初始化,因此 C++ 编译器无法完全优化它。这不会发生。
编译器可以忽略有关函数调用的所有其他内容,而不是实际编译函数调用本身,但编译器必须做任何它需要做的事情来进行安排,以便初始化静态 class 成员好像调用了函数。
如果编译器可以确定程序中没有其他任何东西实际使用静态class成员,并且完全删除它没有观察到的效果,那么编译器可以删除静态class成员,以及初始化它的函数(因为没有其他东西引用该函数)。
请注意,即使获取函数(或 class 成员)的地址也会产生可观察到的效果,因此即使实际上没有任何东西调用函数,但某些东西会获取函数的地址,它不能就此消失。
P.S。 -- 以上所有假设 C++ 代码中没有未定义的行为。随着未定义的行为进入画面,所有规则都消失了 window。
模板实例化是代码的一个函数,而不是任何一种动态运行时间条件的函数。举个简单的例子:
template <typename T> void bar();
void foo(bool b) {
if (b) {
bar<int>();
} else {
bar<double>();
}
}
bar<int>
和 bar<double>
都在此处实例化,即使 foo
从未被调用或即使 foo
仅被 true
调用。
对于变量模板,具体规则为[temp.inst]/6:
Unless a variable template specialization has been explicitly instantiated or explicitly specialized, the variable template specialization is implicitly instantiated when it is referenced in a context that requires a variable definition to exist or if the existence of the definition affects the semantics of the program.
在你的函数中:
friend void fn()
{
(void)AnchorObjT<123>;
};
AnchorObjT<123>
在需要定义的上下文中被引用(无论 fn()
是否曾经被调用,甚至在这种情况下,甚至可以调用),因此它被实例化.
但是AnchorObjT<123>
是一个全局变量,所以它的实例化意味着我们有一个在main()
之前构造的对象——当我们进入main()
、AnchorObjT<123>
的构造函数将是 运行,将 StaticInt
设置为 123
。请注意,我们实际上不需要 运行 fn()
来调用此构造函数 - fn()
在这里的作用只是实例化变量模板,其构造函数在其他地方调用。
打印 123 是正确的预期行为。
请注意,虽然该语言要求全局对象 AnchorObjT<123>
存在,但链接器可能仍然是该对象,因为没有对它的引用。假设您的真实程序对这个对象做更多的事情,如果您需要它存在,您可能需要对它做更多的事情以防止链接器删除它(例如 gcc 有 used
attribute)。
简短的回答是有效。
长的答案是它有效除非 linker 丢弃你的整个翻译单元 (.obj)。
当您创建 .lib 并 link 它时,可能会发生这种情况。 linker 通常会根据 "do I use symbols that obj exports".
的依赖关系图从库中选择哪个 .obj 到 link
因此,如果您在 cpp 文件中使用此技术,则该 cpp 文件没有在您的可执行文件的其他地方使用的符号(包括通过您的 lib 中的其他 obj 间接使用的符号,这些符号又由可执行文件使用),link用户可能会丢弃您的 obj 文件。
我用 clang 体验过这个。我们在哪里创建自注册工厂,有些在哪里被删除。为了解决这个问题,我们创建了一些宏,这些宏导致了一个微不足道的依赖关系的存在,从而防止 obj 文件被丢弃。
这与其他答案并不矛盾,因为 link 一个库的过程是在决定你的程序中什么是什么不是。
这是我的代码:
#include <iostream>
class MyBaseClass
{
public:
static int StaticInt;
};
int MyBaseClass::StaticInt = 0;
template <int N> class MyClassT : public MyBaseClass
{
public:
MyClassT()
{
StaticInt = N;
};
};
template <int N> static MyClassT<N> AnchorObjT = {};
class UserClass
{
friend void fn()
{
std::cout << "in fn()" << std::endl; //this never runs
(void)AnchorObjT<123>;
};
};
int main()
{
std::cout << MyBaseClass::StaticInt << std::endl;
return 0;
}
输出为:
123
...指示 MyClassT()
构造函数被调用,尽管 fn()
从未被调用。
在 gcc
和 clang
上测试 -O0
、-O3
、-Os
甚至 -Ofast
问题
根据 C++ 标准,此程序是否具有未定义的行为?
换句话说:如果更高版本的编译器设法检测到 fn()
永远不会被调用,他们可以优化模板实例化以及 运行 构造函数吗?
能否以某种方式使此代码具有确定性,即强制构造函数 运行 - 不 引用函数名称 fn
或模板参数值 123
在 UserClass
?
更新: 版主 t运行 回答了我的问题并建议进一步 t运行cation。详细原版可以查看here.
"If later versions of compilers manage to detect that fn() will never be called [and] they optimize away template instantiation" 那么那些编译器就会被破坏。
C++ 编译器可以自由实现任何优化没有明显效果。在您概述的情况下,至少会有一个可观察到的效果:即静态 class 成员不会被构造和初始化,因此 C++ 编译器无法完全优化它。这不会发生。
编译器可以忽略有关函数调用的所有其他内容,而不是实际编译函数调用本身,但编译器必须做任何它需要做的事情来进行安排,以便初始化静态 class 成员好像调用了函数。
如果编译器可以确定程序中没有其他任何东西实际使用静态class成员,并且完全删除它没有观察到的效果,那么编译器可以删除静态class成员,以及初始化它的函数(因为没有其他东西引用该函数)。
请注意,即使获取函数(或 class 成员)的地址也会产生可观察到的效果,因此即使实际上没有任何东西调用函数,但某些东西会获取函数的地址,它不能就此消失。
P.S。 -- 以上所有假设 C++ 代码中没有未定义的行为。随着未定义的行为进入画面,所有规则都消失了 window。
模板实例化是代码的一个函数,而不是任何一种动态运行时间条件的函数。举个简单的例子:
template <typename T> void bar();
void foo(bool b) {
if (b) {
bar<int>();
} else {
bar<double>();
}
}
bar<int>
和 bar<double>
都在此处实例化,即使 foo
从未被调用或即使 foo
仅被 true
调用。
对于变量模板,具体规则为[temp.inst]/6:
Unless a variable template specialization has been explicitly instantiated or explicitly specialized, the variable template specialization is implicitly instantiated when it is referenced in a context that requires a variable definition to exist or if the existence of the definition affects the semantics of the program.
在你的函数中:
friend void fn() { (void)AnchorObjT<123>; };
AnchorObjT<123>
在需要定义的上下文中被引用(无论 fn()
是否曾经被调用,甚至在这种情况下,甚至可以调用),因此它被实例化.
但是AnchorObjT<123>
是一个全局变量,所以它的实例化意味着我们有一个在main()
之前构造的对象——当我们进入main()
、AnchorObjT<123>
的构造函数将是 运行,将 StaticInt
设置为 123
。请注意,我们实际上不需要 运行 fn()
来调用此构造函数 - fn()
在这里的作用只是实例化变量模板,其构造函数在其他地方调用。
打印 123 是正确的预期行为。
请注意,虽然该语言要求全局对象 AnchorObjT<123>
存在,但链接器可能仍然是该对象,因为没有对它的引用。假设您的真实程序对这个对象做更多的事情,如果您需要它存在,您可能需要对它做更多的事情以防止链接器删除它(例如 gcc 有 used
attribute)。
简短的回答是有效。
长的答案是它有效除非 linker 丢弃你的整个翻译单元 (.obj)。
当您创建 .lib 并 link 它时,可能会发生这种情况。 linker 通常会根据 "do I use symbols that obj exports".
的依赖关系图从库中选择哪个 .obj 到 link因此,如果您在 cpp 文件中使用此技术,则该 cpp 文件没有在您的可执行文件的其他地方使用的符号(包括通过您的 lib 中的其他 obj 间接使用的符号,这些符号又由可执行文件使用),link用户可能会丢弃您的 obj 文件。
我用 clang 体验过这个。我们在哪里创建自注册工厂,有些在哪里被删除。为了解决这个问题,我们创建了一些宏,这些宏导致了一个微不足道的依赖关系的存在,从而防止 obj 文件被丢弃。
这与其他答案并不矛盾,因为 link 一个库的过程是在决定你的程序中什么是什么不是。