Using static variable along with templates
Asked Answered
P

5

18

I have a template class defined in a header file like this. Here I have defined a static variable as well:

#ifndef TEST1_H_
#define TEST1_H_

void f1();

static int count;

template <class T>
class MyClass
{
public:

    void f()
    {
        ++count;
    }


};

#endif

And I have defined main() function in a different cpp file like this:

int main(int argc, char* argv[])
{
    MyClass<int> a;
    a.f();
    f1();

    cout<<"Main:" << count << "\n";

    return 0;
}

I have implemented function f1() in a different cpp file like this:

void f1()
{
    MyClass<int> a;
    a.f();

    cout<<"F1: " <<count <<"\n";
}

When I compiled this using VC6, I got the output as "F1:0 Main:2". How is this possible? Also, in general how should I handle if I want to use static variables along with templates?

Pointtopoint answered 3/3, 2009 at 17:8 Comment(0)
S
23

You're getting two copies of the same variable because you've declared a static variable in a header file. When you declare a global variable static this way, you're saying it's local to the compilation unit (the .o file). Since you include the header in two compilation units, you get two copies of count.

I think what you really want here is a static template member variable associated with each instance of the template class. It would look like this:

template <class T>
class MyClass
{
    // static member declaration
    static int count;
    ...
};

// static member definition
template<class T> int MyClass<T>::count = 0;

This will get you a count for each instantiation of your template. That is, you'll have a count for MyClass<int>, MyClass<foo>, MyClass<bar>, etc. f1() would now look like this:

void f1() {
    MyClass<int> a;
    a.f();

    cout<<"F1: " << MyClass<int>::count <<"\n";
}

If you want a count for all instantiations of MyClass (regardless of their template parameters), you do need to use a global variable.

However, you probably don't want a global variable directly because you run the risk of using it before it gets initialized. You can get around this by making a global static method that returns a reference to your count:

int& my_count() {
    static int count = 0;
    return count;
}

Then accessing it from within your class like this:

void f() {
    ++my_count();
}

This will ensure that count gets initialized before it's used, regardless of which compilation unit you access it from. See the C++ FAQ on static initialization order for more details.

Synthesis answered 3/3, 2009 at 17:28 Comment(3)
FYI, there used to be a bug in VC6 that caused it to get multiple definitions of static variables in template classes. This happened because it generated multiple instances of the template code that referenced different instances of the variable. I think it's gone with the last service pack.Worrywart
Thanks..thats exactly what I wanted to do, I wanted to use static variable with each instance of the template instantiation..Pointtopoint
global ints are initialized in a completely safe way. there's no need to my_count() (which has possibly lazy thread-safe initialization overhead)Francophobe
O
3

Putting the static declaration in a header file will cause each .cpp file to get its own version of the variable. So the two cout statements are printing different variables.

Overfeed answered 3/3, 2009 at 17:11 Comment(2)
I'm afraid I have to disagree with that! Have a look at pastebin.com/NF4LR5a9 ; it seems to work for me. But having said that, if the template was to be instantiated differently ( eg: C<int> ) then the new instance will have a new static countBoston
The original question had the static variable in global scope, i.e., not in a class. Your example has it inside a template class. The two cases are quite different.Overfeed
G
1

Were you expecting "F1:1 Main:1"? You instantiated MyClass<int> in two separate translation units (i.e. two object files), and the linker saw that there was a duplicate template instantiation, so it discarded the instantiation that was in f1's object file.

Are you passing /OPT:ICF or /OPT:REF to the VC6 linker? That might be related to the duplicate template instantiation removal (or not; duplicate template instantiations might be a special case, compared to ordinary duplicate functions). GCC seems to do something similar on some platforms.

Anyway, I wouldn't rely on this behavior being consistent across compilers. Also, changing the order of object files on the linker command line might affect which instantiation gets discarded.

Greenway answered 4/3, 2009 at 7:7 Comment(1)
I wanted to use static along with my template class for which I will use the accepted answer. I had thought that putting static in the header file will be a bad idea but couldn't figure out why 2 & 0 and not 1 & 1. Thanks for the explanation.Pointtopoint
S
0

There is another solution, you can create a shared parent class and put this static variable in it, then make your template class inherit it privately, here's an example:

class Parent
{
protected: 
    static long count;
};

long Parent::count = 0;

template<typename T>
class TemplateClass: private Parent
{
private: 
    int mKey;
public:
    TemplateClass():mKey(count++){}
    long getKey(){return mKey;}
}

int main()
{
    TemplateClass<int> obj1;
    TemplateClass<double> obj2;

    std::cout<<"Object 1 key is: "<<obj1.getKey()<<std::endl;
    std::cout<<"Object 2 key is: "<<obj2.getKey()<<std::endl;

    return 0;
}

Output will be:

Object 1 key is: 0 
Object 2 key is: 1
Superconductivity answered 18/12, 2016 at 15:58 Comment(0)
S
-1

I think this is actually undefined behaviour.

According to C++14 [basic.def.odr]/6:

There can be more than one definition of a [...] member function of a class template [...] in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then

  • each definition of D shall consist of the same sequence of tokens; and
  • in each definition of D, corresponding names, looked up according to 3.4, shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (13.3) and after matching of partial template specialization (14.8.3), except that a name can refer to a non-volatile const object with internal or no linkage if the object has the same literal type in all definitions of D, and the object is initialized with a constant expression (5.19), and the object is not odr-used, and the object has the same value in all definitions of D; [...]

The problem is that in the first .cpp file, the name count within f1 refers to a different object than the name count within f1 in the second .cpp file, thus violating the condition that corresponding names should refer to the same entity.

They are different objects because of the static specifier which says that each translation unit gets its own object with that name.

Syndesis answered 16/11, 2015 at 10:26 Comment(1)
At a guess, I'd say the rationale for this is to not make linkers' jobs too difficult: the linker could either have both refer to the same count, or both refer to different count s.Syndesis

© 2022 - 2024 — McMap. All rights reserved.