C++ Shared Library with Templates: Undefined Symbols Error

C++ Shared Library with Templates: Undefined symbols error

In addition to the other answers, you can explicitly instantiate template classes. This is only useful if you know beforehand what types the template parameters may assume. You instantiate the template with all these types in the library.

For your example to compile, just add the following to the end of shared.cpp:

// Instantiate myclass for the supported template type parameters
template class myclass<int>;
template class myclass<long>;

This instantiates the template with Type=int and places the instantiated code in the shared library. Add as many explicit instantiations as you need, for all the types you need.

Again, if you want to be able to instantiate the template with any arbitrary Type parameter, then you must add the definitions to the header file, so that the compiler knows the source code of the template when instantiating it in other compilation units.

Undefined symbols linker error with simple template class

The template definition (the cpp file in your code) has to be included prior to instantiating a given template class, so you either have to include function definitions in the header, or #include the cpp file prior to using the class (or do explicit instantiations if you have a limited number of them).

Template subclass of non-template base from shared library causing Undefined symbols typeinfo for 'class' link error

It looks like you are building your shared library with "-fvisibility=hidden" and mark exported function with __attribute__((visibility("default"))) however in order to export RTTI you need to mark exported class with __attribute__((visibility("default"))). From gcc referece:

Note that the type visibility is applied to vague linkage entities associated with the class (vtable, typeinfo node, etc.). In particular, if a class is thrown as an exception in one shared object and caught in another, the class must have default visibility. Otherwise the two shared objects are unable to use the same typeinfo node and exception handling will break.

Shared Library: Undefined Reference with Partial Template Specialization and Explicit Template Instantiation

It turned out to be a false alarm. Nevertheless, this catch took me a couple of hours to finally remember why this symbol might be invisible to consumers. It's really trivial but I feel like posting it here for future visitors who happen to have the same setup. Basically, if you use either a linker script [1] or a (pure) version script [2] (specified with the --version-script linker option), then don't forget to set global visibility for those tpl::foo* third-party-based symbols (or whichever they are in your case). In my case, I originally had the following:

{
global:
extern "C++" {
ml::*;
typeinfo*for?ml::*;
vtable*for?ml::*;
};

local:
extern "C++" {
*;
};
};

what I clearly had to change to

{
global:
extern "C++" {
tpl::foo*;
ml::*;
typeinfo*for?ml::*;
vtable*for?ml::*;
};

local:
extern "C++" {
*;
};
};

in order to link everything properly and get the expected result.

Hope this helps and regards.

BONUS


A curious reader could ask though, "Why the hell are you combining explicit visibility attributes and a linker/version script to control visibility of symbols when there are already the -fvisibility=hidden and -fvisibility-inlines-hidden options which are supposed to do just that?".

The answer is that they of course do, and I indeed do use them to build my shared libraries. However, there is one catch. It is a common practice to link some internal libraries (privately) used by your shared library statically (into that library), primarily in order to completely conceal such dependencies (keep in mind, though, that header files accompanying your shared library should also be properly designed to implement this). The benefits are clear: clean and controllable ABI and reduced compile times for shared library consumers.

Take for example, Boost, as the most widespread candidate for such a use case. Encapsulating all of the heavily templated code from Boost privately into your shared library and eliminating any Boost symbols from the ABI will greatly reduce interface pollution and compile times of your shared library consumers, not including the fact that your software components will also look professionally developed.

Anyway, to the point, it turns out that unless those static libraries that you want to link into your shared library were themselves also built with the -fvisibility=hidden and -fvisibility-inlines-hidden options (what would be a ridiculous expectation as nobody is going to distribute static libraries with hidden interface symbols by default as it defeats their purpose), their symbols will inevitably still be visible (for instance, through nm -CD <shared-library>) regardless of the fact that you're building the shared library itself with those options. That is, in this case, you have two options to resolve it:

  1. Manually rebuild those static libraries (your shared library dependencies) with the -fvisibility=hidden and -fvisibility-inlines-hidden options, what is clearly not always possible/practical given their potential third-party origin.
  2. Use linker/version script (like it is done above) to supply at link time in order to instruct linker to forcefully export/hide proper symbols from your shared library.

Shared library symbol lookup template instantiation

I have confirmed using nm -gDC lib.so that the library contains the symbol and spelled it exactly the same in my lookup attempt but it can't be found.

As Igor Tandetnik correctly commented, the name the library actually exports is the C++ mangled name, and not rosidl_typesupport_cpp::get_service_type_support_handle<example_interfaces::srv::AddTwoInts> (which is the demangled name).

To see the actual symbol name, use nm -D lib.so (in particular, omit the -C flag).

Undefined symbols with extern templates in a static library

Using your github code I can reproduce it with GCC 5.0 or Clang 3.4 but not Clang 3.6 (built from svn).

When it fails Foo.cpp.o does not contain a definition of qux::Foo<int, 2ul>::Foo(int const&)

$ nm -C Foo.cpp.o 
U qux::Foo<int, 2ul>::Foo(int const&)
0000000000000000 W qux::Foo<int, 2ul>::Foo()
U qux::Foo<int, 2ul>::Foo(int const&)
0000000000000000 W qux::Foo<int, 2ul>::Foo()
0000000000000000 W qux::Foo<int, 2ul>::operator[](unsigned long) const
0000000000000000 W std::array<int, 2ul>::operator[](unsigned long) const
0000000000000000 W std::__array_traits<int, 2ul>::_S_ref(int const (&) [2], unsigned long)

But using Clang 3.6 that symbol is defined in

$ nm -C Foo.cpp.o 
0000000000000000 W qux::Foo<int, 2ul>::Foo(int const&)
0000000000000000 W qux::Foo<int, 2ul>::Foo()
0000000000000000 W qux::Foo<int, 2ul>::Foo(int const&)
0000000000000000 W qux::Foo<int, 2ul>::Foo()
0000000000000000 n qux::Foo<int, 2ul>::Foo(int const&)
0000000000000000 n qux::Foo<int, 2ul>::Foo()
0000000000000000 W qux::Foo<int, 2ul>::operator[](unsigned long) const
0000000000000000 W std::array<int, 2ul>::operator[](unsigned long) const
0000000000000000 W std::__array_traits<int, 2ul>::_S_ref(int const (&) [2], unsigned long)
0000000000000000 W std::array<int, 2ul>::end()
0000000000000000 W std::array<int, 2ul>::data()
0000000000000000 W std::array<int, 2ul>::begin()
0000000000000000 W int* std::__addressof<int>(int&)

I'm not sure what the problem is, it might be a bug in Clang which has now been fixed, but it's strange that GCC has the same bug.

The function that causes the problem uses C++14's relaxed constexpr rules (looping in a constexpr function), so I assume it's a bug in the compilers' implementation of that new feature.

Undefined reference error for template method

Templated code implementation should never be in a .cpp file: your compiler has to see them at the same time as it sees the code that calls them (unless you use explicit instantiation to generate the templated object code, but even then .cpp is the wrong file type to use).

What you need to do is move the implementation to either the header file, or to a file such as VAConfig.t.hpp, and then #include "VAConfig.t.hpp" whenever you use any templated member functions.

Undefined reference from shared library in static one

Static libraries are just an agglomeration of object files (their members), perhaps with a ranlib(1) generated index.

On Linux, if you link an object file (3) foo.o with a static library (2) libee.a and a shared library (1) libyz.so and if you pass -rdynamic at link time (i.e. gcc -rdynamic foo.o libee.a libyz.so -o myprog or gcc -rdynamic foo.o -lee -lyz -o myprog) then dynamic linker would resolve the get_object name at dynamic link time (in ld-linux.so)

Details are explained in ELF wikipage and Drepper's paper: How To Write Shared Libraries. Read also Levine's book: Linkers and loaders & ld(1) man page.



Related Topics



Leave a reply



Submit