Exporting Vtables and Typeinfo without Exporting the Whole Class in GCC
The Problem
You're working with a large C++ codebase, and you want to expose certain methods of a class to other modules without having to export the entire class itself. This can be especially useful when you want to keep the internal structure of your class hidden from external users, but still allow them to interact with specific functionalities.
However, you run into a roadblock: exporting the methods requires the vtable and typeinfo of the class, which are typically tied to the class's definition. This begs the question: how do you export only the selected methods, along with their associated vtable and typeinfo, without exposing the entire class's definition?
The Scenario
Let's illustrate with a simple example:
// Class.cpp
#include "Class.h"
class Class {
public:
void publicMethod() { /* ... */ }
int privateMethod() { /* ... */ }
private:
int privateData;
};
// Class.h
#ifndef CLASS_H
#define CLASS_H
class Class; // Forward declaration
void usePublicMethod(Class* obj); // Function using publicMethod
#endif
In this scenario, we only want to allow external code to call publicMethod()
. The usePublicMethod()
function in Class.h
illustrates this requirement. However, exporting publicMethod()
will also require exporting the vtable and typeinfo for Class
, which would inevitably expose the privateMethod()
and privateData
member.
The Solution: GCC's __attribute__((visibility("hidden")))
GCC provides a powerful attribute, __attribute__((visibility("hidden")))
, that allows you to control the visibility of symbols (functions, variables, classes) during linking. By applying this attribute to the class definition, we can hide it from external modules while still exporting the desired methods.
// Class.cpp
#include "Class.h"
__attribute__((visibility("hidden"))) class Class {
public:
void publicMethod() { /* ... */ }
int privateMethod() { /* ... */ }
private:
int privateData;
};
void usePublicMethod(Class* obj) {
obj->publicMethod();
}
void exportPublicMethod() {
// Export only the publicMethod symbol
// This ensures vtable and typeinfo for publicMethod are exported
// without exporting the entire class definition.
__attribute__((visibility("default"))) void (*p)(Class*) = &Class::publicMethod;
}
Here's a breakdown of the solution:
- Hide the class: The
__attribute__((visibility("hidden")))
attribute is applied to theClass
definition. This makes the entire class invisible to external modules. - Export the desired method: We create a function
exportPublicMethod()
which uses a pointer-to-function to point toClass::publicMethod()
. This function is then marked with__attribute__((visibility("default")))
, ensuring it is visible externally. - Exporting the Vtable and Typeinfo: By exporting
publicMethod()
through a pointer-to-function, GCC automatically exports the necessary vtable and typeinfo information required for its proper invocation. This is achieved without exposing the entireClass
definition.
Advantages
- Information Hiding: This approach allows you to effectively hide the internal details of your class from external users, enhancing modularity and encapsulation.
- Code Reusability: You can reuse the same class definition for different parts of your codebase, each with varying levels of exposure.
- Reduced Complexity: The code remains cleaner and easier to understand, as you don't have to resort to complex workarounds to achieve selective visibility.
Limitations
- Compiler-Specific: The
__attribute__((visibility("hidden")))
attribute is a GCC-specific feature. Other compilers might have different syntax or functionality for controlling symbol visibility. - Dynamic Linking: This approach might not be suitable for scenarios where you need to dynamically load and unload code modules, as the exported symbol might not be correctly resolved.
Conclusion
By leveraging GCC's __attribute__((visibility("hidden")))
, you can selectively export methods from a class without exposing the entire class definition. This approach promotes information hiding, code reusability, and overall clarity in your codebase. Remember to consider compiler-specific limitations and explore alternative solutions if needed.