Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to separate definition from the declaration for a class template using 'extern' in C++11 in a library (dll, so,..)

Tags:

I am a developer of an Open Source library. One of our classes is templated for a bunch of different types. Currently the definition resides in the header file, which has a negative effect on compilation times and also forces users to include more headers than needed. My goal is the following:

  • To reduce compilation time, I want to make use of the the explicit instantiation declaration introduced with C++11.

  • The definition of the class methods and members, which all are static, must be separate from the declaration in an implementation file. They should be available for usage outside and inside the library without users having to do an explicit instantiation definition or anything like this.

  • This must run cross-platform on all common compilers that support C++11 (Visual Studio 2013+, GCC, etc)

C++11 is providing new features for class templates, in specific the "explicit instantiation declaration". As far as I understand this can be used in this context. Previous questions dealt with this in similar contexts, e.g. How to use extern template and Separating definition/instantiation of template classes without 'extern' but those do not deal with library exports and their solution cause linker errors if a client tries to use the shared library.

Currently I have managed to implement this in a way it compiles, links and runs on Visual Studio 2015, but I am unsure if I use the keywords correctly, especially the __declspec one in this case. This is what I got (simplified):

// class.h template<typename T> class PropertyHelper;  template<typename T> class PropertyHelper<const T> { public:     typedef typename PropertyHelper<T>::return_type return_type;      static inline return_type fromString(const String& str)     {         return PropertyHelper<T>::fromString(str);     }      static const int SomeValue; };   template<> class EXPORTDEF PropertyHelper<float> { public:     typedef float return_type;      static return_type fromString(const String& str);      static const int SomeValue; };  extern template EXPORTDEF class PropertyHelper<float>; 

The last line is the explicit instantiation declaration. As far as I understand this means that clients do not have to declare this themselves every time. EXPORTDEF is a define that is either __declspec(dllexport) or __declspec(dllimport) on Windows. I am not sure if I need to place this in the line above, because the following also compiles, links and runs:

extern template class PropertyHelper<float>; 

The cpp file looks like this:

const int PropertyHelper<float>::SomeValue(12);  PropertyHelper<float>::return_type PropertyHelper<float>::fromString(const String& str) {     float val = 0.0f;      if (str.empty())         return val;      //Some code here...      return val; }  template class PropertyHelper<float>; 

The last line is the explicit instantiation definition.

So my question is most of all if I did everything correctly here according to the C++11 standard and secondly (if first is true) if the __declspec keyword is redundant in the context of the explicit instantiation declaration, or what I should do about it, as I did not find proper information in the MSDN docs.

like image 483
Ident Avatar asked Aug 17 '15 16:08

Ident


People also ask

How do you define a template function outside class?

You can define template methods outside the class definition, in the same header, without using inline and without receiving multiple definition errors. the error no longer occurs.

What is extern template?

Extern templatesA template specialization can be explicitly declared as a way to suppress multiple instantiations. For example: #include "MyVector. h" extern template class MyVector<int>; // Suppresses implicit instantiation below --

How a template is declared?

A class template must be declared before any instantiation of a corresponding template class. A class template definition can only appear once in any single translation unit. A class template must be defined before any use of a template class that requires the size of the class or refers to members of the class.


2 Answers

The standard (from working draft of C++0x through working draft in 2014), section 14.7.2 describes Explicit instantiation, stating that there are two forms of explicit instantiation, definition and declaration. It says, "an explicit instantiation declaration begins with the extern keyword." It further specifies that declarations, using extern, do not generate code.

Care must be taken to ensure the declarations are issued within the namespace of the template class declaration, or specifically reference the namespace in the qualified name, as in:

namespace N {  template< class T > void f( T& ) {} }  template void N::f<int>(int &); 

Instantiating a template function, and generating it's code (definition). Whereas:

extern template void N::f<int>(int &); 

Instantiates a template function for type int as a declaration, but does not generate code. The extern keyword informs the compiler that the code will be provided at link time from another source (possibly a dynamic library, but the standard doesn't discuss that platform specific concept).

Further, it is possible to instantiate members and member functions selectively, as in:

namespace N { template<class T> class Y { void mf() { } }; }  template void N::Y<double>::mf(); 

This generates the code only for the function mf(), for doubles. It is, therefore, possible to declare instantiations (using extern), and then define instantations (without extern) for specific parts of a template type. One could elect to generate code or members for some portions of a template class within every compilation unit (inline), and force the generation of other portions of code into a particular compilation unit or library.

IBM's Knowledge Center article for their XLC V 11.1 compiler, supporting draft C++0x, discusses the strategy for using the extern keyword when building libraries. From their example, and the draft standards documents over several years (which have been consistent from 2008 forward on this subject), it's clear that extern has limited applicability to the specifics of dynamic libraries, but in general is limited to controlling where generated code is placed. The author will still be required to adhere to platform specific demands relative to dynamic linking (and loading). That is beyond the purpose of the extern keyword.

Extern is equally applicable to static libraries or dynamic libraries, but the limitation on a library design is significant.

Say a template class declaration, presented in a header file, exists like:

   namespace N     {      template< typename T >      class Y         {            private:            int x;           T v;            public:            void f1( T & );           void f2( T &, int );         };         } 

Next, in a CPP file:

namespace N {  template< typename T> void Y<T>::f1( T & ) { .... }  template< typename T> void Y<T>::f2( T &, int ) { .... } } 

Now consider the potential uses of Y. Consumers of the library may only require instantiations of Y for int, float and double. All other uses would be of no value. This is a design point of the author of the library, not some general notion about this concept. For whatever reason, the author only supports those three types for T.

To that end, explicit instantiation declarations may be included in the header file

extern template class N::Y< int >; extern template class N::Y< float >; extern template class N::Y< double >; 

As this is processed by the user's various compilation units, the compiler is informed there will be code generated for these three types, but the code is not generated in each compilation unit as the user builds. Indeed, if the author doesn't include the CPP file defining the functions f1 and f2 for template class Y, the user wouldn't be able to use the libary.

Assuming, for the moment, a static library is the intended product regarding template class Y (for simplification of this discussion), the author compiles the static library with the CPP defining functions f1 and f2, along with the explicit instantiation definitions:

template class N::Y< int >; template class N::Y< float >; template class N::Y< double >; 

This will cause the code to be generated for template class Y on behalf of these three types, creating a static library. User code will now have to link to this library, but do nothing else to use the classes. Their compilation units will not generat code for template class Y, instead incorporating that code from the library.

The same concept applies to a dynamic library, but platform specifics regarding function declarations, dynamic loading and dynamic linking are not found in the standards for C++ through 2014 working drafts, regarding C++0x, C++11 or C++14, at present. The extern keyword in explicit template instantiations is limited to creating declarations, it's absence creates definitions (where code is generated).

This brings up the question regarding users of such a library intent on using Y for unsigned long, char, or some other type not provided in a dynamic or static library. The author has the choice of refusing to support this by not distributing source for the generation of code (the function definitions of f1 and f2 for template class Y). However, if the author did wish to support such usage, distributing that source, instructions would be required for the user to generate a new library to replace the existing one, or generating a second library for the additional types.

For either case, it would be wise to separate the explicit instantiation definitions in a CPP file which includes the header declaring template class Y, the including a header of the funtion definitions of f1 and f2 for template class Y (as opposed to the practice of including a CPP file, which could also work). In this way, the user would create a CPP file that includes the header for template class Y, then the function definitions for template class Y, then issuing the new explicit instantiation definitions:

#include "ydeclaration.h" // the declaration of template class Y #include "ydefinition.h"  // the definition of template class Y functions (like a CPP)  template class N::Y< unsigned long >; template class N::Y< char >; 

For a static library, little else would be required, and the user could opt to build the additional compilation unit within their project, obviating the need for a static library target.

However, if the user wanted to build a dynamic library, care would be required regarding the platform specific code regarding dynamic libraries on a particular platform. Specifically on Windows, for example, this may mean explicitly loading the new dynamic library.

Considering the complications involved with creating dynamic libraries, it is a wonder anyone ever does. Sometimes there simply is no other choice. Key to the decision is to determine exactly why a dynamic library should be used. In the ancient epoch of computers with under 1 GByte of RAM, one of the justifications was saving memory by sharing code, but for any particular library, what is the probability that code sharing would result in saving RAM? For something as common as the C runtime, or Windows MFC DLL's, it may be highly probable. Libraries which provide highly targeted services, on the other hand, are more likely to be used by only one running program.

One really good purpose is the concept of plug in behaviors. Browsers, IDE's, photo editing software, CAD software and others benefit from an entire industry of applications distributed as plugins to existing products, which are distributed as dynamic libraries.

Another justification is distributing updates. While this is an attractive theory, the practice can cause more problems than it's worth.

Another common justification is "modularity". To what end, though? Separating compilation units already reduces compile times. Dynamic libraries will affect link times more than compile times, but is that worth the additional complexity?

Otherwise, however, providing dynamic libraries, especially for a fairly small product, is not really worth the trouble.

An entire book could be written on the subject of writing portable dynamic libraries applicable to both Windows and Linux.

On Windows, the choice of using __declspec(dllexport/dllimport) can apply to an entire class. It's important to realize, however, that whatever compiler is used to generate the DLL can only be used with targets built with that same compiler, or compatible compilers. Within the MS VC lineage, many versions are NOT compatible with each other at this level, so a DLL built with one version of Visual Studio may not be compatible with other versions, causing a burden on the author to generate DLL's for every possible compiler/version to be supported.

There are similar issues regarding static libraries. Client code must link with the same version and configuration of the CRT as the DLL is built with (is the CRT statically linked?). Client code must also elect the same exception handling settings and RTTI settings as the library is built with.

When portability to Linux or UNIX (or Android/iOS) is to be considered, the problems magnify. Dynamic linking is a platform specific concept not handled in C++.

Static libraries would probably be the best approach, and for those __declspec(dllexport/dllimport) should not be used.

With all that said against dynamic libraries, here's one of the many ways to implement that in Windows (completely inapplicable to Linux/UNIX/etc).

A simple (perhaps naive but convenient) approach is to delcare the entire class as exported from a DLL (imported in client code). This has the slight advantage over declaring each function as exported or imported, because this approach includes class data, and just as importantly, AUTOMATIC assignment/destructor/constructor code C++ may build for your class. That could be vitally important if you didn't carefully attend to those and export them manually.

In a header to be included for the DLL production:

#define DLL_EXPORT // or something similar, to indicate the DLL is being built 

That will be included at the top of your header declaring your library's template classes. The header declaring DLL_EXPORT is only used in a project configured to compile the DLL library. All client code will import an otherwise empty version. (Myriad Other methods for doing this exist).

As such, DLL_EXPORT is defined when building for the DLL, not defined when building client code.

In the header for your library's template class declarations:

#ifdef _WIN32 // any Windows compliant compiler, might use _MSC_VER for VC specific code #ifdef DLL_EXPORT #define LIB_DECL __declspec(dllexport) #else #define LIB_DECL __declspec(dllimport) #endif 

Or whatever you prefer to see in place of LIB_DECL as a means of declaring entire classes exportable from DLL's, imported in client code.

Proceed with class declarations as:

namespace N     {      template< typename T >      struct LIB_DECL Y         {            int x;           T v;           std::vector< T > VecOfT;            void f1( T & );           void f2( T &, int );         };         } 

Explicit instantiation declarations for this would be:

extern template struct LIB_DECL N::Y< int >; extern template struct LIB_DECL N::Y< float >; extern template struct LIB_DECL N::Y< double >;  extern template class LIB_DECL std::vector< int >; extern template class LIB_DECL std::vector< float >; extern template class LIB_DECL std::vector< double >; 

Take note of the std::vector used in class Y in this example. Consider the problem carefully. If your DLL library uses std::vector (or any STL class, this is just an example), the implementation you used at the time you build the DLL must match what the user chooses when they build client code. The 3 explicit instantiations of vector match the requirements of the template class Y, and instantiate std::vector within the DLL, and with this declaration become exportable from the DLL.

Consider, how would the DLL code USE std::vector. What would generate the code in the DLL? It's obvious from experience that the source for std::vector is inline - it's a header only file. If your DLL does instantiate vector's code, how would client code access it? Client code would "see" std::vector source and attempt it's own inline generation of that code where the client access std::vector functions. If, and only if, the two are an exact match would that work. Any difference between the source used to build the DLL and the source used to build the client would differ. If client code had access to the std::vector in template class T, there would be chaos if the client used a different version or implementation (or had different compiler settings) when using std::vector.

You have the option of explicitly generating std::vector, and informing client code to use THAT generated code by declaring std::vector as an extern template class, to be imported in client code (exported in DLL builds).

Now, in the CPP where the DLL is built - the function definitions of the library - you must explicitly instantiate definitions:

template struct LIB_DECL N::Y< int >; template struct LIB_DECL N::Y< float >; template struct LIB_DECL N::Y< double >;  template class LIB_DECL std::vector< int >; template class LIB_DECL std::vector< float >; template class LIB_DECL std::vector< double >; 

In some examples, like MS KB 168958, they suggest making the extern keyword a define, modifying this plan as:

#ifdef _WIN32 // any Windows compliant compiler, might use _MSC_VER for VC specific code #ifdef DLL_EXPORT #define LIB_DECL __declspec(dllexport) #define EX_TEMPLATE #else #define LIB_DECL __declspec(dllimport) #define EX_TEMPLATE extern #endif 

So that in the header file for both DLL and client builds, you may simply state

EX_TEMPLATE template struct LIB_DECL N::Y< int >; EX_TEMPLATE template struct LIB_DECL N::Y< float >; EX_TEMPLATE template struct LIB_DECL N::Y< double >;  EX_TEMPLATE template class LIB_DECL std::vector< int >; EX_TEMPLATE template class LIB_DECL std::vector< float >; EX_TEMPLATE template class LIB_DECL std::vector< double >; 

While this has the advantage of issuing these lines once, in the header, I personally prefer observing the extern keyword clearly in use in the header, so that I know without a doubt the only place code generation can take place is in the CPP of the DLL build (where they appear, a second time, without the extern keyword). In this way, the extern's in the header are forward declarations, which do not conflict with the explicit instantiation definitions in the CPP, and it avoids obfuscating the extern keyword when used in the client code. It is, perhaps, a peculiar preference of my own.

You may be thinking, "what about other client code and std::vector". Well, it's important to consider. Your header file includes std::vector, but remember, your DLL is built with the code available to YOU at compile time. Your client will have their own header, it within like versions of VC, that should be the same. SHOULD is not a good plan, though. It could be different. They may just assume VC 2015 is the same and plow ahead. Any difference, be it object layout, actual code..anything, could doom the running application with very strange effects. If you export your version, they client would be well advised to include the explicit instantiation declarations throughout all their compilation units, so everything uses YOUR version of std::vector...but, there's a serious catch.

What if some other library did this, too, with yet a different version of std::vector?

It makes the use of STL a bit nasty in these contexts, so there's one fairly good design choice that eliminates this. Don't expose any use of STL.

If you keep all use of STL private to your library, and never expose an STL container to client code, you're probably in the clear. If you choose that in design, you have no need to explicitly instantiate the std::vector (or any STL) in your library.

I included the example so as to discuss it, how it's documented by MS (KB 168958), and why you probably don't want to do it. However, the reverse scenario comes up, too.

In the original inquiry, the use of std::string (or one of it's alternatives) is to be used. Think of this: in the DLL, how will that use of std::string instantiate? What if there is any difference between the std::string code available when the DLL was built compared to that which is used by the client whey they build? The client could, after all, elect to use some other STL than provided by MS. Of course, you could stipulate they not do that, but...perhaps you could explicitly instantiate std::string as extern WITHIN your DLL. In that way, you have no STL code built within the DLL, and the compiler is now informed that it should find that code built by the client, not within the DLL. I suggest it for research and thought.

The insidious problem you face is this: this will all work on your machine, in your tests, because you're using one compiler. It would be flawless there, but could fail spectacularly in client builds because of code differences, or setting differences, subtle enough to escape warnings.

So, let's assume you agree and skip the last three lines in the examples which instantiate std::vector...is it done?

That depends on your IDE settings, which I'll leave to you. The question centered around the use of __declspec(dllxxxxx) and it's usage, and there are several ways to implement its use, I focused on one. Whether or not you must explicitly load the library, rely on automatic dynamic linking features, consider the DLL_PATH...these are general DLL building topics you either know, or are beyond the real scope of the question.

like image 115
JVene Avatar answered Sep 30 '22 20:09

JVene


Here is a quote from the standard:

For a given set of template arguments, if an explicit instantiation of a template appears after a declaration of an explicit specialization for that template, the explicit instantiation has no effect. [...]

So your explicit instantiation declaration

extern template EXPORTDEF class PropertyHelper<float>; 

has no effect since it appears after the explicit specialization

template<> class EXPORTDEF PropertyHelper<float> ... 

Same with your explicit instantiation definition

template class PropertyHelper<float>; 

in the cpp file (I assume that cpp file includes class.h, since otherwise the code there wouldn't compile).

Are you sure you want to explicitly specialize PropertyHelper<float>?

like image 21
Daveed V. Avatar answered Sep 30 '22 21:09

Daveed V.