未使用的函数能否根据C ++ 14实例化具有副作用的变量模板?

时间:2018-12-08 13:43:40

标签: c++ templates c++14 standards unused-variables

这是我的代码:

#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()被调用。

gccclang上使用-O0-O3-Os甚至-Ofast进行了测试


问题

此程序是否符合C ++标准的未定义行为?

换句话说:如果更高版本的编译器设法检测到不会fn()被调用,他们是否可以在运行构造函数的同时优化模板实例化?

可以通过某种方式使此代码具有确定性,即强制构造函数运行-引用函数名称fn或{{1}之外的模板参数值123 }?


更新:主持人截断了我的问题,并建议进一步截断。可以查看原始详细版本here

3 个答案:

答案 0 :(得分:3)

模板实例化是代码的功能,而不是任何类型的动态运行时条件的功能。作为一个简单的例子:

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

  

除非已明确实例化或明确指定了变量模板专用化,否则在需要存在变量定义的上下文中引用变量模板专用化或定义的存在影响程序语义时,将隐式实例化变量模板专用化

在您的职能中:

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)。

答案 1 :(得分:2)

“如果更高版本的编译器设法检测到永远不会调用fn(),并且它们优化了模板实例化,则这些编译器将被破坏。

C ++编译器可以自由实施任何没有明显效果的优化 。在您概述的情况下,至少会有一个可观察到的效果:即,静态类成员没有得到构造和初始化,因此C ++编译器无法完全优化这一点。不会发生的。

编译器可以忽略有关函数调用的所有其他信息,而不能实际编译函数调用本身,但是编译器必须做任何必要的事情来进行安排,以便静态类成员得到初始化 就像 那样进行了该函数调用。

如果编译器可以确定程序中没有其他东西实际使用静态类成员,并且完全删除它没有可观察到的效果,那么编译器可以删除静态类成员以及对其进行初始化的函数(因为没有其他东西引用该函数)。

请注意,即使使用一个函数(或类成员)的地址也将产生可观察到的效果,因此,即使实际上没有调用该函数,但是某些东西占用了该函数的地址,也不能随便去离开。

P.S。 -以上所有假设都假定C ++代码中没有未定义的行为。通过未定义的行为输入图片,所有规则都将消失在窗口之外。

答案 2 :(得分:1)

简短的答案是可行的。

长答案是可行的,除非链接器放弃整个翻译单元(.obj)。

在创建.lib并将其链接时,可能会发生这种情况。链接器通常根据“我是否使用obj导出的符号”的依赖关系图从lib中选择要链接的.obj。

因此,如果您在cpp文件中使用此技术,则该cpp文件中没有可在可执行文件中其他位置使用的符号(包括通过lib中的其他obj间接由可执行文件使用),链接器可能会丢弃yoir obj文件。

我曾在c里经历过。我们在哪里创建自注册工厂,有些在哪里丢弃。为了解决这个问题,我们创建了一些宏,这些宏导致存在琐碎的依赖关系,从而防止obj文件被丢弃。

这与其他答案没有矛盾,因为链接lib的过程是在确定程序中是什么,而不是什么。