What is the rational behind the fact that compiling
namespace ns __attribute__((visibility("default"))) {
template<typename T>
inline int func1(const T& x) {
return x;
}
inline int func2(int x) {
return x;
}
struct test {
template<typename T>
int func3(const T &x) { return x; }
int func4(int x) { return x; }
};
}
int __attribute__((visibility("default"))) client(int x) {
ns::test t;
const int y1 = ns::func1(x);
const int y2 = ns::func2(x);
const int y3 = t.func3(x);
const int y4 = t.func4(x);
return y1 + y2 + y3 + y4;
}
with
g++ -Wall -fPIC \
-fvisibility=hidden -fvisibility-inlines-hidden \
-shared -o libtest.so test.cpp
yields a library exporting ns::test::func1<int>()
and ns::test::func3<int>()
but not ns::func2()
nor ns::test::func4()
? Both template functions are defined inline
and -fvisibility-inlines-hidden
tells the compiler to hide them — or at least their instantiations which hopefully are inline too.
Hiding the function templates func1
and func3
explicitly, i.e. with
template<typename T>
int __attribute__((visibility("hidden"))) func(const T &x) { return x; }
leads to the expected behavior. Omitting the default visibility in the namespace definition hides the two instantiations.
Background: We try to minimize the amount of visible symbols in our library. Thus we're using the mentioned compiler flags and attributes. Of course this is also necessary for all static third party libraries. But unfortunately those inline template functions inside included header files are completely out of our control. For example, every instantiation of
namespace std {
template<typename _CharT, typename _Traits, typename _Alloc>
inline bool
operator==(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
const _CharT* __rhs)
{ return __lhs.compare(__rhs) == 0; }
}
from #include <string>
will happily generate a publicly visible symbol inside my library. And the most annoying part, it will be put inside my library without any version information, so no GLIBCXX_3.x.z
to differentiate.
Bonus Question: What's the overall impact using a linker script like
{
global:
_Z6clienti;
local:
*;
};
As far as I understand, this is only really feasible, if I don't use any exception handling nor dynamic type casting across the boundaries of my library. But what happens with those inline functions? It feels like this whole hidden visibility thing violates the one definition rule anyway, thus it's not a big deal.
Explicit instantiation of a function template or of a member function of a class template cannot use inline or constexpr .
Limitations of Inline FunctionsInline functions do not work if the body of the function contains any sort of looping or iteration. Inline functions do not support the use of switch or goto statements. C++ Inline functions cannot work if the function defined is recursive in nature.
Inline functions provide following advantages: 1) Function call overhead doesn't occur. 2) It also saves the overhead of push/pop variables on the stack when function is called. 3) It also saves overhead of a return call from a function.
inline functions might make the code faster, they might make it slower. They might make the executable larger, they might make it smaller. They might cause thrashing, they might prevent thrashing. And they might be, and often are, totally irrelevant to speed.
GCC did not respect -fvisibility-inlines-hidden
for template functions; it was a bug that was fixed starting from gcc-10.
With GCC 10, all four functions have hidden visibility (command line flag takes precedence over visibility attribute specified on the enclosing namespace).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With