Introduction to Reflection in C++
Reflection is a mechanism in programming to implement generic code that can work for all types of objects. It helps to recognize the format for the objects at runtime and invoke the methods of that object and access the fields of these objects. This feature is employed in various tasks, such as invocation or remote methods, where a descriptor for the specific class is returned, offering information about its class, variables, and methods. Using these descriptors itself, one can easily invoke instance methods and access their variables.
Syntax
Using a reflection API in one’s application, the below syntax can be used.
class demo {
public:
int x;
char* ptr;
double m;
protected:
long my_arr[10];
A** ptr1;
A* gptr;
public:
RTTI_DESCRIBE_STRUCT((RTTI_FIELD(x, RTTI_FLD_PUBLIC),
RTTI_PTR(ptr, RTTI_FLD_PUBLIC),
RTTI_FIELD(m, RTTI_FLD_PUBLIC),
RTTI_ARRAY(my_arr, RTTI_FLD_PROTECTED),
RTTI_PTR_TO_PTR(ptr1, RTTI_FLD_PROTECTED),
RTTI_PTR(gptr, RTTI_FLD_PROTECTED)));
};
In the above syntax of a class describing a class descriptor, various flags have been used in the macros defined for the class. As we can see, two types of macros are being used –
- RTTI_DESCRIBE_STRUCT: This helps to define the components of the class. It is used within the declaration of the class.
- RTTI_REGISTER_STRUCT: This macro helps to register the class descriptor in the repository and thus must be used in the implementation file of type .cpp.
Description of a class can be done using the below macros
- RTTI_FIELD: This field is the type of scalar or structure.
- RTTI_PTR: This field describes the pointer to the above scalar or the structure.
- RTTI_PTR_TO_PTR: This macro is a double pointer to the macro RTTI_FIELD.
- RTTI_ARRAY: This macro is used for one-dimensional arrays of scalar classes or structures.
The second parameter for the class requires flags or qualifiers for those fields. Below are some of the flags that can be used in the above macros.
enum RTTIFieldFlags {
RTTI_FLD_INSTANCE = 0x0001,
RTTI_FLD_STATIC = 0x0002,
RTTI_FLD_CONST = 0x0004,
RTTI_FLD_PUBLIC = 0x0010,
RTTI_FLD_PROTECTED = 0x0020,
RTTI_FLD_PRIVATE = 0x0040,
RTTI_FLD_VIRTUAL = 0x0100, // This macro is used for virtual base classes
RTTI_FLD_VOLATILE = 0x0200,
RTTI_FLD_TRANSIENT = 0x0400
};
Syntax for describing a method is as given below:
class Second : public First {
int i;
public:
virtual void meth();
char* xyz(char* ptr);
RTTI_DESCRIBE_CLASS(Second, (RTTI_BASE_CLASS(First, RTTI_FLD_PUBLIC),
RTTI_FIELD(i)),
(RTTI_PROC(meth, RTTI_FLD_PUBLIC|RTTI_MTH_VIRTUAL),
RTTI_FUNC(xyz, RTTI_FLD_PUBLIC)));
};
How Reflection works C++?
While talking about reflection in C++, one can easily detect if the expression used in the application is valid or not and also if that object contains the mentioned member variable or method or not.
While running the program, this API collects all the information. It creates a descriptor for the class that contains all the information about the member variables and methods of the class. Compilers use this class descriptor to check if the variables and methods belong to that particular class or not and also if the given expression is valid or not.
Code:
#include <string>
#define REFLECT(x) template<class R> void reflect(R& r) { r x; }
struct Employee {
std::string emp_id;
int salary;
REFLECT(
("Emp_id", emp_id)
("Salary", salary)
)
};
#include <iostream>
class Demo {
std::ostream& output;
bool flag;
public:
Demo(std::ostream& output) : output(output)
{}
template<class T>
auto write(T& myobj) -> decltype(myobj.reflect(*this), void()) {
output << "{";
flag = false;
myobj.reflect(*this);
output << "}\n";
}
void write(int val) {
output << val;
}
void write(std::string& val) {
output << '"' << val << '"';
}
template<class T>
Demo& operator()(const char* emp_id, T& myfield) {
if (flag) {
output << ",";
}
flag = true;
output << emp_id << ":";
write(myfield);
return *this;
}
};
int main() {
Demo myObj(std::cout);
Employee emp1 = { "2324354", 90000 };
myObj.write(emp1);
Employee emp2 = { "235566", 50000 };
myObj.write(emp2);
}
Output:
Advantages and Disadvantages
Below are mentioned the advantages and disadvantages:
Advantages
- Parse the debugging information.
- Using special preprocessors that build class descriptors by parsing C++ sources.
- Manually by the programmer.
Disadvantages
- Extracting debugging information- Using reflection API, extracting the complete information related to the type of objects being used in the program becomes easier. Here one must not change the program while using this feature.
- No extra steps required- When utilizing reflection features to obtain information about the format type of an object, no additional steps are needed to generate runtime type information.
- Efficient code generation:- Reflection API in an application helps generate an efficient code for reflection methods.
- Access variables and instance methods: – The descriptor returned using reflection API in a class one can easily access the variables of the class and invoke the instance methods of the class using that descriptor.
- Locate class globally:- One finds it easier to locate a class and its variables and methods using the given class descriptor from anywhere in the application.
Conclusion
The addition of reflection as a tool is great for determining the object type at runtime, as it significantly aids in various tasks such as invocation, debugging, remote method execution, serialization, and more. This also helps a lot to locate the object in an application using its name directly or if one needs to iterate through all the components.
Recommended Articles
This is a guide to Reflection in C++. Here we discuss an introduction to Reflection in C++, syntax, how it works with advantages, disadvantages, and sample code. You can also go through our other related articles to learn more –