acm
acm

Reputation: 12727

Should visibility/export macros be applied to templates when writing a library?

When building a C++ DLL or shared library __attribute__((__visibility__("default"))) or __declspec(dllexport) is frequently attached via a macro to those concrete symbols (classes, functions, etc) that should be made available to consumers of the library, with the other symbols defaulted to have internal visibility.

But what should be done about inline functions or templates?

It seems that for inline functions the answer should be that no annotation is needed. If the consumer of the header defining the inline function actually inlines the function, then there is no symbol needed. If the consumer emits an out-of-line definition instead, that is still OK. The only wrinkle is that the definitions of the inline function inside the DLL and inside each consuming library may differ. So, you could get some trouble if you were expecting to reliably compare the addresses of inline functions, but that seems pretty sketchy anyway.

Given that argument, it would seem that since templates bodies are typically entirely visible to the consuming TU, that the same logic would apply.

I feel like there are probably some subtleties here with respect to 'extern template' and explicit instantiations.

Does anyone have concrete guidance on how visibility attributes should adhere to inline functions and templates?

Upvotes: 8

Views: 2091

Answers (2)

ajclinto
ajclinto

Reputation: 145

I think what you are asking boils down to these 2 questions:

When do I need to explicitly export a symbol with __attribute__((__visibility__("default")))?

The rule here is that if the implementation for the method is internal to your shared library (usually, it's in the .cpp file with a declaration in the external .h) you need to mark the declaration for that symbol as externally visible when compiling your shared library. If you fail to do so, compiling anything that uses that method in an external library will complain at link time - so this problem is easy enough to catch with testing.

This is assuming you've also added -fvisibility=hidden to internalize all symbols by default.

Is it harmful to add __attribute__((__visibility__("default"))) to inline functions or template functions with a definition that is externally visible?

It's not, since a common use case for default visibility is to mark entire classes for export - which may comprise both outline and inline methods. In this situation, inline methods will be inline (in any translation unit that uses it) and generate no external symbol. If you later choose to outline the method, the symbol visibility will take effect.

For templates, explicit template instantiation is just a mechanism to allow you to create an outline definition for the template method - and the same rules apply as for ordinary inline/outline methods.

Upvotes: 0

rodrigo
rodrigo

Reputation: 98348

Inline functions are not externally visible (have no linkage, IIRC), so they are not exportable from a DLL. If they are to be public, then they are fully written in the header file of your library and every user recompiles it.

And as you say in the question, since the inlined code is recompiled in every module that uses the library, so for future versions of the library there may be problems.

My advice for inline functions in a shared library is that they should be used only for really trivial tasks or for absolutely generic functions. Note that converting a public inline function to a non-inline function is an ABI breaking change.

For example:

  • A memcpy-like funcion. Inline!
  • A bswap-like function. Inline!
  • A class constructor. Do not inline! Even if it does nothing now, You may want to do something in a future version of the library. Write a non-inline empty constructor in the library and export it.
  • A class destructor. Do not inline! Same as above.

The fact that an inline function can have several different addresses, in practice, has little importance.

About extern template and explicit instantiations, with a bit of care, they can be used to export a template from a library. If the template instantiations are limited to a specific set of cases, you can even avoid to copy the template code to the header files.

NOTE 1: In the following examples I will use a simple function template, but a class template will work exactly the same. NOTE 2: I'm using the GCC syntax. The MSC code is similar, I think you already know the differences (and I don't have a MSC compiler around to test).

Example 1

public_foo.h

template<int N> int foo(int x); //no-instantiable template

shared_foo.cpp

#include "public_foo.h"

//Instantiate and export

template __attribute__ ((visibility("default")))
int foo<1>(int x);

template __attribute__ ((visibility("default")))
int foo<2>(int x);

program.cpp

#include "public_foo.h"

int main()
{
    foo<1>(42); //ok!
    foo<2>(42); //ok!
    foo<3>(42); //Linker error! this is not exported and not instantiable
}

If instead, your template should be freely instantiable, but you expect it to be frequently used in a particular way, you can export these ones from the library. Think of std::basic_string: it is most likely to be used as std::basic_string<char> and std::basic_string<wchar_t>, but unlikely as std::basic_string<float>.

Example 2

public_foo.h

template<int N> int foo(int x)
{
    return N*x;
}

//Do not instantiate these ones: they are exported from the library
extern template int foo<1>(int x);
extern template int foo<2>(int x);

shared_foo.cpp

#include "public_foo.h"

//Instantiate and export

template __attribute__ ((visibility("default")))
int foo<1>(int x);

template __attribute__ ((visibility("default")))
int foo<2>(int x);

program.cpp

#include "public_foo.h"

int main()
{
    foo<1>(42); //ok, from library
    foo<2>(42); //ok, from library
    foo<3>(42); //ok, just instantiated
}

Upvotes: 5

Related Questions