3

Consider this synthetic example. I have two native C++ projects in my Visual Studio 2010 solution. One is console exe and another is lib.

There are two files in lib:

// TImage.h

template<class T> class TImage
{
public:
  TImage()
  {
#ifndef _LIB
    std::cout << "Created (main), ";
#else
    std::cout << "Created (lib), ";
#endif
    std::cout << sizeof(TImage<T>) << std::endl;
  }

#ifdef _LIB
  T c[10];
#endif
};

void CreateImageChar();
void CreateImageInt();

and

// TImage.cpp

void CreateImageChar()
{
  TImage<char> image;
  std::cout << sizeof(TImage<char>) << std::endl;
}
void CreateImageInt()
{
  TImage<int> image;
  std::cout << sizeof(TImage<int>) << std::endl;
}

And one file in exe:

// main.cpp

int _tmain(int argc, _TCHAR* argv[])
{
  TImage<char> image;
  std::cout << sizeof(TImage<char>) << std::endl;

  CreateImageChar();
  CreateImageInt();

  return 0;
}

I know, I shouldn't actually do like this, but this is just for understanding what is happening. And that's, what happens:

// cout:
Created (main), 1
1
Created (main), 1
10
Created (lib), 40
40

So how exactly this happened, that linker overrides lib's version of TImage<char> with exe's version of TImage<char>? But since there is no exe's version of TImage<int>, it preserves lib's version of TImage<int>?.. Is this behavior standardized, and if so, where can I found the description?

Update: Explanations of the effect given below are correct, thanks. But the question was "how exactly this happened"?.. I expected to get some linker error like "multiply defined symbols". So the most suitable answer is from Antonio Pérez's reply.

Community
  • 1
  • 1
Mikhail
  • 20,685
  • 7
  • 70
  • 146
  • I believe your program is technically ill-formed, and does not have to compile at all. Templates can [and generally must] violate the one-definition rule, but the multiple definitions have to be identical. Your `TImage` violates that rule. – Dennis Zickefoose Jul 29 '11 at 07:43

6 Answers6

2

Template code creates duplicated object code.

The compiler copies the template code for the type you provide when you instance the template. So when TImage.cpp is compiled you get object code for two versions of your template, one for char and one for int in TImage.o. Then main.cpp is compiled and you get a new version of your template for char in main.o. Then the linker happens to use the one in main.o always.

This explains why your output yields the 'Created' lines. But it was a little bit puzzling to see the mismatch in lines 3, 4 regarding object's size:

Created (main), 1
10

This is due to the compiler resolving the sizeof operator during compile-time.

Antonio Pérez
  • 6,702
  • 4
  • 36
  • 61
  • Your explanation is right. It fails to address his puzzlement, about this behavior for a library. The simple note is that it is a static library and static libraries do not make a difference from putting all your sources in one project. (On Windows) We wrote the answers at the same time, see my answer for the library part... – rioki Jul 29 '11 at 08:15
  • "Then the linker happens to use the one in `main.o` **always**." - Why, exactly?.. Why no errors like "multiply defined symbol"? – Mikhail Jul 29 '11 at 11:23
  • Not sure about it. Maybe Sean Farrell has an answer for that. – Antonio Pérez Jul 29 '11 at 11:39
1

I am assuming here that you are building a static library, because you do not have any __decelspec(dllexport) or extern "C" in the code. What happens here is the following. The compiler create an instance of TImage<char> and TImage<int> for your lib. It also creates an instance for the your executable. When the linker joins the static library and the objects of the executable together duplicate code gets removed. Note here that static libraries are linked in like object code, so it does not make any difference if you create one big executable or multiple static libraries and an executable. If you would build one executable the result would be dependent on the order the objects are linked in; aka "not defined".

If you change the library to a DLL the behavior changes. Since you are calling over the boundary of a DLL, each needs their copy of TImage<char>. In most cases DLLs behave more as you would expect a library to work. Static libraries are normally just a convenience, so you need not put the code into your project.

Note: This only applies on Windows. On POSIX systems *.a files behave like *.so file, which creates quite some head aches for compiler developers.

Edit: Just never ever pass the TImage class over a DLL boundary. That will ensure a crash. That is the same reason why Microsoft's std::string implementation crashes when mixing debug and release builds. They do exactly what you did only with the NDEBUG macro.

rioki
  • 5,988
  • 5
  • 32
  • 55
0

Memory layout is a compile-time concept; it has nothing to do with the linker. The main function thinks TImage is smaller than the CreateImage... functions do, because it was compiled with a different version of TImage.

If the CreateImage... function were defined in the header as inline functions, they would become part of main.cpp's compilation unit, and would thus report the same size characteristics as main reports.

This also has nothing to do with templates and when they get instantiated. You'd observe the same behaviour if TImage was an ordinary class.

EDIT: I just noticed that third line of cout doesn't contain "Created (lib), 10". Assuming it's not a typo, I suspect what's happening is that CreateImageChar is not inlining the call to the constructor, and is thus using main.cpp's version.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
0

The compiler will always instantiate your template when you use it - if the definition is available.

This means, it generates the required functions, methods etc. for the desired specialization and places them in the object file. This is the reason why you either need to have the definition available (usually in a header file) or an existing instantiation (e.g. in another object file or library) for the specific specialization that you are using.

Now, when linking, a situation might occur which is usually not allowed: more than one definition per class/function/method. For templates, this is specifically allowed and the compiler will choose one definition for you. That is what is happening in your case and what you call "overriding".

sstn
  • 3,050
  • 19
  • 32
0

Template creates duplicates of classes (ie space) during the compilation itself. So when you are using too much of templates, the smart compilers try to optimize them based on parametrization of templates.

0

Within your library, you have a TImage that prints "lib" on construction, and contains an array of T. There are two of these, one for int and one for char.

In main, you have a TImage that prints "main" on construction, and does not contain an array of T. There is only the char version in this case; because you never ask for the int version to be created.

When you go to link, the linker choses one of the two TImage<char> constructors as the official one; it happens to chose main's version. This is why your third line prints "main" instead of "lib"; because you are calling that version of the constructor. Generally, you don't care which version of the constructor gets called... they are required to all be the same, but you have violated that requirement.

That's the important part: your code is now broken. Within your library functions, you expect to see that array of char within TImage<char> but the constructor never creates it. Further, imagine if you say new TImage<char> within main, and pass the pointer to a function within your library, where it gets deleted. main allocates one byte of space, the library function tries to release ten. Or if your CreateImageChar method returned the TImage<char> it creates... main will allocate one byte on the stack for the return value, and CreateImageChar will fill it with ten bytes of data. And so on.

Dennis Zickefoose
  • 10,791
  • 3
  • 29
  • 38
  • True, the code is bogus, but he knows that. Your answer fails to answer his question. The way he wrote the code, it will actually work quite well. Just don't pass that instance of TImager over a DLL boundary. – rioki Jul 29 '11 at 08:08
  • @Sean: My first three paragraphs answers the question. And no, it won't work quite well, even without DLLs. Just two translation units with different definitions for the same name. – Dennis Zickefoose Jul 29 '11 at 08:15