I am trying to build a shared library and want to export only the relevant symbols from it. Any private implementation details should not be exported.
This however fails when it comes to any of the special member functions like the default constructor as illustrated by the following sample project:
CMakeLists.txt:
cmake_minimum_required (VERSION 3.12)
project (visibility)
include (GenerateExportHeader)
add_library (library SHARED
include/detail/impl.hpp
include/library.hpp
src/detail/impl.cpp
src/library.cpp
)
set_target_properties (library PROPERTIES CXX_VISIBILITY_PRESET "hidden")
target_include_directories (library PUBLIC
$<BUILD_INTERFACE:${visibility_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${visibility_BINARY_DIR}/include>
)
generate_export_header (library EXPORT_FILE_NAME
${visibility_BINARY_DIR}/include/export.hpp
)
add_executable (test test/test.cpp)
target_link_libraries (test PRIVATE library)
impl.hpp:
#include <export.hpp>
struct LIBRARY_NO_EXPORT Impl {
Impl();
};
impl.cpp:
#include <detail/impl.hpp>
Impl::Impl() {/*maybe some custom implementation is necessary*/}
library.hpp:
#include <detail/impl.hpp>
#include <export.hpp>
class LIBRARY_EXPORT Library {
public:
//Library(); // ctor has to be declared and defined explicitly
private:
Impl i_;
};
library.cpp:
#include <library.hpp>
//Library::Library() = default; // ctor has to be declared and defined explicitly
test.cpp:
#include <library.hpp>
int main() {
Library{};
return 0;
}
Building this project leads to the following linker error:
/usr/bin/ld: CMakeFiles/test.dir/test/test.cpp.o: in function `Library::Library()':
[..] undefined reference to `Impl::Impl()'
This error can be fixed by uncommenting the commented lines so that Library defines it's own default constructor. Just writing Library() = default; in the header or implementing it inline is not sufficient.
Apparently special members being implicitly defined means they are not defined unless they are used. So Library::Library() is only generated as soon as main() tries to create an instance of Library where the symbols from Impl are not available. Library::Library() is not generated while compiling library.cpp (which makes kinda sense since it's empty).
However, if i add a static method foo() to Library and implement it in library.cpp as void Library::foo() { Library{}; } the linker error persists. That means even though a default constructor of Library is generated as part of the library, it is not exported.
Another observation which feels rather wrong is that removing the custom constructor from Impl also gets rid of the linker error. But then it is also possible to create an instance of Impl in main(), which should not be the case when Impl is not exported.
Of course different compilers/linkers have different opinions about this. Compiling with GCC or clang throws the error mentioned above while MSVC just warns about Impl missing a dll-interface but everything works fine.
Is what I am observing intended behavior? If yes, is there any way to force the compiler to generate all special members while building the shared library apart from manually declaring them and then defining them as default?
Some additional information why I am looking for an alternative solution which are not essential to the core question:
In the real project all 6 special members of several classes are affected which would lead to several hundred lines of code that basically say "default".
Additional problems occur if Library is a template class. Here it is also required to define the special member functions in a .cpp file. Only instantiating the template with all feasible types is not enough to generate special members:
library_template.hpp:
template<class T> class Library {
public:
Library();
private:
Impl i_;
};
extern template class LIBRARY_EXPORT Library<float>;
library_template.cpp:
template<class T>
Library<T>::Library() = default;
template class Library<float>;
Which gets increasingly ugly when all I want to say is "just default everything".