6

目前我正在开发一个 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<T>= T"要导出的类"。例子:

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

whereExample的静态成员函数exportToLua()包含特定于类的注册代码。我的理解是,每个编译单元都存在一个静态成员变量的实例ExportToLua<T>::m_registered——即每个T.

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

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

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

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

感谢您的输入,

最好的,克里斯托夫

4

2 回答 2

4

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

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

14.7.1p1 ...类模板特化的隐式实例化导致类成员函数、成员类、范围成员枚举、静态数据成员和成员模板的声明的隐式实例化,但不会导致定义或默认参数的隐式实例化;它会导致无范围成员枚举和成员匿名联合的定义的隐式实例化。

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

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

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

         ExportToLua() { (void)&m_registered; }

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

于 2013-09-02T10:45:56.697 回答
3

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

#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;
};

它似乎在这个演示中工作。

编辑:正如 DyP 正确指出的那样,单一定义规则在这里发挥作用,无论是否ExportToLua<T>::m_registered被实例化。

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

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

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

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

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

于 2013-09-02T10:46:11.437 回答