Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit initialization of static member variables for template classes

Currently I am working on a C++ project in which I plan to embed Lua scripts. For that reason certain classes need to be exported to Lua and I wanted to make this more convenient therefore I created a template class:

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

Now every class that needs to be exported is derived from ExportToLua<T> with T="the class to be exported". Example:

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

where Example's static member function exportToLua() holds the class-specific registration code. My understanding is that an instance of the static member variable ExportToLua<T>::m_registered exists for every compile unit - that is - for every T.

But when I start my program the registration code never gets called. For example in example.cpp:

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

however I never see this message when I run my program.

Any idea why? Is the compiler some "optimizing away" the static variable m_registered, because I am not using it anywhere?

Thanks for your input,

Best, Christoph

like image 951
chris.schuette Avatar asked Sep 02 '13 10:09

chris.schuette


People also ask

How do you initialize static members of a class?

We can put static members (Functions or Variables) in C++ classes. For the static variables, we have to initialize them after defining the class. To initialize we have to use the class name then scope resolution operator (::), then the variable name. Now we can assign some value.

Can we call static member function of a class using object of a class?

A static member function can be called even if no objects of the class exist and the static functions are accessed using only the class name and the scope resolution operator ::. A static member function can only access static data member, other static member functions and any other functions from outside the class.

Can static variables be initialized?

A static variable in a block is initialized only one time, prior to program execution, whereas an auto variable that has an initializer is initialized every time it comes into existence. A static object of class type will use the default constructor if you do not initialize it.

How do you initialize static constant characteristics of a class?

To initialize the const value using constructor, we have to use the initialize list. This initializer list is used to initialize the data member of a class.


2 Answers

If the compiler implicitly instantiates a class template that contains static members, those static members are not implicitly instantiated. The compiler will instantiate a static member only when the compiler needs the static member's definition.

This behaviour is backed by the C++ standard and here is the passage

14.7.1p1 ... The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, scoped member enumerations, static dataembers and member templates; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions.

and another relevant section found by @gx_

14.7.1p8 The implicit instantiation of a class template does not cause any static data members of that class to be implicitly instantiated.

A work around is the one mentioned by @gx_: Simply add

         ExportToLua() { (void)&m_registered; }

to the constructor. Taking the address forces the instantiation of the static variable m_registered.

like image 176
chris.schuette Avatar answered Sep 30 '22 14:09

chris.schuette


You already found the reason in the standard why the behavior is the way it is. So as a workaround, you can 'trick' the compiler into instantiating that static member by referencing it from either the template constructor or destructor.

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

It seems to work in this demo.

Edit: As DyP correctly pointed out, the One-Defintion-Rule comes into play here in whether ExportToLua<T>::m_registered gets instantiated or not.

To guarantee implicit instantiation, make sure you meet at least one of the following conditions:

  • Provide a definition for either the constructor or destructor of the class that's to be exported.
  • You create an instance of that class that's used elsewhere in other parts of your code. This will force the compiler to provide a default ctor if you didn't provide one thereby triggering the necessary template instantiations.

If none of those conditions can be met for whatever reason then you'll need to explicitly instantiate the members you want from the template. For example,

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

You can wrap that into a macro to make it nicer to use if desired.

like image 24
greatwolf Avatar answered Sep 30 '22 15:09

greatwolf