【问题标题】:Implicit initialization of static member variables for template classes模板类的静态成员变量的隐式初始化
【发布时间】:2013-09-02 10:09:45
【问题描述】:

目前我正在开发一个 C++ 项目,我计划在其中嵌入 Lua 脚本。出于这个原因,某些类需要导出到 Lua,我想让这更方便,因此我创建了一个模板类:

template <class T>
class ExportToLua {
    public:
        ExportToLua() {}
        ~ExportToLua() {}
    private:
        static int m_registered;
};
template <class T> int ExportToLua<T>::m_registered = T::exportToLua();

现在每个需要导出的类都是从ExportToLua&lt;T&gt; 派生的,T="要导出的类"。示例:

 class Example: public ExportToLua<Example> {
 public:
     Example();
     virtual ~Example();
     static int exportToLua();
 private:
 };

其中Example 的静态成员函数exportToLua() 保存了特定于类的注册码。我的理解是每个编译单元都存在一个静态成员变量ExportToLua&lt;T&gt;::m_registered 的实例——也就是说——每个T

但是当我启动我的程序时,注册码永远不会被调用。例如在example.cpp中:

 int Example::exportToLua() {
     std::cout << "int Example::exportToLua()" << std::endl;
     return -2;
 }

但是,当我运行我的程序时,我从未看到此消息。

知道为什么吗?编译器是否对静态变量 m_registered 进行了一些“优化”,因为我没有在任何地方使用它?

感谢您的意见,

最好, 克里斯托夫

【问题讨论】:

  • 也许你必须在class Example的编译单元(源文件)中显式实例化基础,即template class ExportToLua&lt;Example&gt;;
  • 在这里回答:stackoverflow.com/a/17132624/688659。活生生的例子:ideone.com/4kTHYg (vs ideone.com/N5wJNE)
  • @gx_ 这个答案远不能令人满意。它指的是一个特定的编译器,并不说明代码的合法性/正确性。
  • 14.7.1p1 ... 类模板特化的隐式实例化会导致类成员函数、成员类、作用域成员的声明的隐式实例化,但不会导致定义或默认参数的隐式实例化枚举、静态数据成员和成员模板;它会导致无范围成员枚举和成员匿名联合的定义的隐式实例化。
  • 感谢标准报价! @Walter 你在这里=)(我现在可以将此权威参考添加到我对另一个问题的回答中)。我认为您可以发布自己问题的答案。

标签: c++ templates inheritance static-members


【解决方案1】:

如果编译器隐式实例化包含静态成员的类模板,则不会隐式实例化这些静态成员。只有当编译器需要静态成员的定义时,编译器才会实例化静态成员。

这种行为受到 C++ 标准的支持,这里是段落

14.7.1p1 ... 类模板特化的隐式实例化导致声明的隐式实例化, 但不是类成员的定义或默认参数 函数、成员类、作用域成员枚举、静态 数据成员和成员模板;它会导致隐式 非作用域成员枚举定义的实例化和 成员匿名工会。

和@gx_找到的另一个相关部分

14.7.1p8 类模板的隐式实例化不会导致该类的任何静态数据成员被隐式实例化。

@gx_ 提到的解决方法是:只需添加

         ExportToLua() { (void)&m_registered; }

到构造函数。获取地址会强制实例化静态变量 m_registered。

【讨论】:

  • 实际上我刚刚(在 N3337 中)找到了另一个相关的引用,在同一部分更远一点:14.7.1p8 类模板的隐式实例化不会导致该类模板的任何静态数据成员要隐式实例化的类。
  • 我认为 [temp.inst]/2 是最合适的 ;) “除非类模板的成员 [...] 已被显式实例化或显式特化,否则该成员的特化是当在需要成员定义存在的上下文中引用特化时隐式实例化;特别是,除非静态数据成员本身被使用,否则不会发生静态数据成员的初始化(以及任何相关的副作用)以需要存在静态数据成员的定义的方式。"
【解决方案2】:

您已经在标准中找到了行为为何如此的原因。因此,作为一种解决方法,您可以通过从模板构造函数或析构函数中引用静态成员来“欺骗”编译器来实例化该静态成员。

#define FORCE_INSTANTIATE(x) (x)
// or (avoids -Wall and -pedantic warnings)
// template <typename T> inline void FORCE_INSTANTIATE(T) {}

template <class T>
class ExportToLua
{
  public:
    ExportToLua() {}
    virtual ~ExportToLua() { FORCE_INSTANTIATE(m_registered); }
  private:
      static int m_registered;
};

它似乎在 this demo 中工作。

编辑:正如 DyP 正确指出的那样,One-Defintion-Rule 在这里发挥作用,无论 ExportToLua&lt;T&gt;::m_registered 是否被实例化。

为保证隐式实例化,请确保您至少满足以下条件之一:

  • 为要导出的类的构造函数或析构函数提供一个定义
  • 您创建了该类的一个实例,该实例在代码的其他部分的其他地方使用。如果您没有提供默认 ctor,这将强制编译器提供一个默认 ctor,从而触发必要的模板实例化。

如果无论出于何种原因,这些条件都不能满足,那么您需要从模板中显式地实例化您想要的成员。例如,

class Example: public ExportToLua<Example>
{
public:
  // ...
  static int exportToLua();
  // etc.
};
template int ExportToLua<Example>::m_registered;

如果需要,您可以将其包装到宏中,以便更好地使用。

【讨论】:

  • 我在 Archlinux x64 机器上使用 GCC 4.8.1 20130725 (prerelease) 从这个演示中没有得到任何输出,甚至没有禁用优化,除非我明确尝试阅读 m_registered,所以我不认为这是一个可靠的解决方法。 -1 暂停时以获得更好的解决方法。
  • 一个更可靠的解决方法是强制构造一个ExportToLua&lt;T&gt; 类型的全局对象,其默认构造函数应该调用T::exportToLua()。标准要求构造函数被调用。这可以通过定义宏#define EXPORT_TO_LUA(X) ExportToLua&lt;X&gt; _##X 来实现,并在您认为应该导出某个类时调用它,例如:EXPORT_TO_LUA(Example)。警告:ExportToLua 不能被继承,但我认为也没有理由这样做。每个要导出的类只能调用一次 EXPORT_TO_LUA。
  • @brunocodutra 我已经在 mingw g++ 4.6.3 和 mingw g++ 4.8.0 上进行了测试,它们都在我的机器上打印输出。我还在 msvc cl v16.00.30319 上对其进行了测试,并且也正确输出了它。将FORCE_INSTANTIATE 更改为模板函数对您有什么改变吗?
  • [stmt.expr]/1: "expression[opt] ;" (an expression-statement) 是丢弃值表达。此外,id-expression m_registered 只能在此处出现在未计算的操作数中(根据 [expr.prim.general]/13)。因此,ODR [basic.def.odr] 不需要定义存在,我怀疑其他任何东西。因此,不强制实例化。模板函数版本不同(并且应该适用于 AFAIK)。
  • @DyP 你已经搞定了,总而言之,如果用户保证创建了至少一个要导出的类的对象,或者它有一个明确定义的析构函数。
猜你喜欢
  • 2021-08-07
  • 1970-01-01
  • 2016-03-10
  • 1970-01-01
  • 1970-01-01
  • 2011-03-14
  • 1970-01-01
  • 2011-01-21
相关资源
最近更新 更多