Templates and static data member initialization

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
That's because you haven't actually defined a AirplaneDataProxy<AirplaneData>::instance.

The template definition in the .cpp file never sees AirplaneData.
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();
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.
Why is your instance a vector and not an instance of the class?
The .cpp file where the definition is placed can see AirplaneData, since it includes the AirplaneData header file.
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.
Cool.
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 
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
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.
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
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.
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
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
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.
That code will give you multiple instances...

I stand corrected. *EDIT*
Last edited on
EDIT 2:

Funny typo and misinterprettation.

Weeeeee
Last edited on
No.
Yes? =P
Topic archived. No new replies allowed.