Templates and static data member initialization

May 13, 2010 at 11:50am
Hi,

I have a very simple singleton class template in a header file, which I report here:

template <class T>
class AirplaneDataProxy {
public:
static std::vector<T>& get() {
return instance;
}

private:
AirplaneDataProxy() { }
~AirplaneDataProxy() { }
AirplaneDataProxy(const AirplaneDataProxy &);
AirplaneDataProxy& operator=(const AirplaneDataProxy&);

static std::vector<T> instance;
};

As for the "instance" static member, I define it in a .cpp file, in the following way:

std::vector<T> AirplaneDataProxy<T>::instance;

Nevertheless, when compiling (I'm using Visual Studio 2008), I get the wollowing linking problem:

error LNK2001: external symbol "private: static class std::vector<class AirplaneData,class std::allocator<class AirplaneData> > AirplaneDataProxy<class ratman::AirplaneData>::instance" unresolved.

I also tried to move the static member definition into the same header file where I declared it. In this latter case, I don't get any linking problem. By the way, since I include the header file in multiple source files, I fear to get multiple member definitions. Am I wrong?

What should I do to get rid of this problem?

Best
Gabriele
May 13, 2010 at 12:05pm
That's because you haven't actually defined a AirplaneDataProxy<AirplaneData>::instance.

The template definition in the .cpp file never sees AirplaneData.
May 13, 2010 at 12:12pm
Thanks for answering, but actually I did not get your answer.

Of course I did not define any AirplaneDataProxy<AirplaneData>::instance, because I'm using a template, hence I defined a:

template <class T>
std::vector<T> AirplaneDataProxy<T>::instance;

which is correct. In fact, if I only compiled this library, I would get no problem at all.
The problem arises when the compiler links this library against the main source object, where basically what I do is:

AirplaneDataProxy<AirplaneData>::get();
May 13, 2010 at 12:17pm
As your definition is in a .cpp file, that file never sees AirplaneData, and so never instantiates AirplaneDataProxy<AirplaneData>::instance.

The other stuff appears to work because it's in a header file.
May 13, 2010 at 12:18pm
Why is your instance a vector and not an instance of the class?
May 13, 2010 at 12:21pm
The .cpp file where the definition is placed can see AirplaneData, since it includes the AirplaneData header file.
May 13, 2010 at 12:25pm
AirplaneDataProxy role is to provide some utility functions about a single vector of generic data. I have two main requirements: to be sure to have singleton vectors, and to handle different vector types. That is, I want only one std::vector<AirplaneData>, only one std::vector<PlacemarkData>> and so on. I know the name "instance" in this case is misleading.
May 13, 2010 at 12:59pm
Cool.
May 13, 2010 at 1:01pm
Template declarations are not the same as normal class declarations.

You must put everything in the header file (.h).

So in your header file you would have this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#ifndef __AIRPLANE_DATA_PROXY_H
#define __AIRPLANE_DATA_PROXY_H

#include <vector>

template<class T>
class AirplaneDataProxy
{
public:
	static std::vector<T>& get()
	{
		return instance;
	}

	AirplaneDataProxy()
	{
	}
	~AirplaneDataProxy()
	{
	}
	AirplaneDataProxy(const AirplaneDataProxy &);
	AirplaneDataProxy& operator=(const AirplaneDataProxy&);

	static std::vector<T> instance;
};

template<class T>
std::vector<T> AirplaneDataProxy<T>::instance;

#endif // __AIRPLANE_DATA_PROXY_H 
May 13, 2010 at 1:06pm
Ok, in fact with such an approach it works. But since I include this header file in multiple source files (e.g. a.cpp, b.cpp, and so on), I was wondering whether I get multiple definitions. In many forums static members definition in header files is strongly deprecated to avoid this problem. Am I wrong?

Thanks
Gabriele
May 13, 2010 at 1:22pm
That is what you would expect but it is the compiler's job to ensure that it does not happen.

I can only assume that the compiler must cooperate with the linker to achieve this.

Otherwise its just magic.
May 13, 2010 at 1:26pm
In fact I fear I need to define somthing to let Visual Studio understand this. I'm used to GNU GCC/G++ and not so confident with MS compilers.

Thanks all for your cooperation.
Gabriele
May 13, 2010 at 1:44pm
That code will give you multiple instances of AirplaneDataProxy<AirplaneData>::instance. One for each .cpp file that compiles it. It isn't quite the right solution.
May 13, 2010 at 2:06pm
That code will give you multiple instances of AirplaneDataProxy<AirplaneData>::instance. One for each .cpp file that compiles it. It isn't quite the right solution.


I'm pretty sure that's not true. It doesn't happen on my machine.

Once instantiated, only one instance of a templated class is used. If one cpp file instantiates Foo<int>, then Foo<int> is externally linked like any other normal non-templated class and other cpp files can reference it.


Here's a trivial program I made to test:

1
2
3
4
5
6
7
8
9
10
11
// header.h
void IncrementT();

template <typename T>
class Foo
{
public:
    static T t;
};

template <typename T> T Foo<T>::t = 0;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// main.cpp
#include <iostream>
#include "header.h"

int main()
{
    std::cout << Foo<int>::t << "\n";  // prints 0 as you'd expect

    IncrementT();

    std::cout << Foo<int>::t << "\n";  // prints 1 as you'd expect

    char ch;
    std::cin >> ch;
    return 0;
}

1
2
3
4
5
6
7
8
// file2.cpp

#include "header.h"

void IncrementT()
{
    Foo<int>::t += 1;
}


Foo<int> is being instantiated in both main.cpp and file2.cpp. Yet changes made to the static member 't' in one file2.cpp are visible in main.cpp, proving that the cpp files are sharing the same instance.

EDIT:


gabriele82rm wrote:
In many forums static members definition in header files is strongly deprecated to avoid this problem. Am I wrong?


Templates don't follow "normal" rules that way (and that's not really "deprecated" as much as it's "not legal" -- try it and you'll likely get linker errors)

Remember that 'Foo' is not a class, it's a template. Foo<int> is the class.

If multiple cpp files instantiate the same class, then the linker will only use one of them, and the others are quietly "dropped". This has other hidden dangers (See below) which is why it's not allowed for normal non-templated classes... but it is a necessary evil in order to make templates work this way. The alternatives are much worse.


As for the dangers of this behavior... it allows for the possibility of functions with the same name but different bodies to be produced without error. Resulting in one of them being quietly "dropped" leading to potentially unexpected behavior.

Here's an example:

1
2
3
4
5
6
7
8
9
10
//header.h
template <typename T>
class Foo
{
public:
    void print() const;
};

void CallFile1(const Foo<int>& v);
void CallFile2(const Foo<int>& v);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// file1.cpp
#include "header.h"
#include <iostream>

template <typename T>
void Foo<T>::print() const
{
    std::cout << "Calling from file1\n";
}

void CallFile1(const Foo<int>& v)
{
    v.print();
}

int main()
{
    Foo<int> a;
    CallFile1(a);
    CallFile2(a);

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// file2.cpp
#include "header.h"
#include <iostream>

template <typename T>
void Foo<T>::print() const
{
    std::cout << "Calling from file2\n";
}

void CallFile2(const Foo<int>& v)
{
    v.print();
}


Notice that both cpp files define their own Foo<T>::print function. Yet there's no linker error.

Also note that only one of them will be called. Which one is called depends entirely on which instantiation the linker decides to keep (ie: flip a coin, phase of the moon, etc)
Last edited on May 13, 2010 at 2:28pm
May 13, 2010 at 2:22pm
That code will give you multiple instances of AirplaneDataProxy<AirplaneData>::instance. One for each .cpp file that compiles it. It isn't quite the right solution.
I'm pretty sure that's not true.

I recall reading about this in C++ Templates: The Complete Guide. Here it is, in section 6.1.2, page 64:
[...]
Because this is an automatic process, a compiler could end up creating two copies in two different files, and some linkers could issue errors when they find two distinct definitions for the same function. In theory, this should not be a concern of ours: It is a problem for the C++ compilation system to accommodate. In practice, things work well most of the time, and we don't need to deal with this issue at all.
[...]


So, I suspect in most cases the compiler and linker have some tricks up their sleeves.
Last edited on May 13, 2010 at 2:25pm
May 13, 2010 at 2:31pm
Right... that basically confirms my point.

The key sentence in that quote:

this should not be a concern of ours: It is a problem for the C++ compilation system to accommodate.


Any compiler/linker worth using does not have this problem.
May 13, 2010 at 2:33pm
That code will give you multiple instances...

I stand corrected. *EDIT*
Last edited on May 13, 2010 at 3:18pm
May 13, 2010 at 2:51pm
EDIT 2:

Funny typo and misinterprettation.

Weeeeee
Last edited on May 13, 2010 at 6:01pm
May 13, 2010 at 2:56pm
No.
May 13, 2010 at 3:00pm
Yes? =P
Topic archived. No new replies allowed.