1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
# Custom classes
Reflection implies that all your fields are public. But in object-oriented programming, you often don't want
that. If your class is more than a trivial, behaviorless struct, you often want to make your fields private.
If you want your class to be supported by reflect-cpp, it needs to have the following:
1) It needs to publicly define a type called `ReflectionType` using `using` or `typedef`.
2) It needs to have a constructor that accepts your `ReflectionType` as an argument.
3) It needs to contain a method called `reflection` that returns said `ReflectionType` (or a reference thereto).
If you class fulfills these three conditions, then it is fully supported by all serialization and deserialization
routines in reflect-cpp.
Please be aware that due to limitations of the Avro format, it is a good idea to always have a struct as your
`ReflectionType` when using Avro to avoid infinite recursions.
If you absolutely do not want to make any changes to your original class, you can implement a [custom parser](https://github.com/getml/reflect-cpp/blob/main/docs/custom_parser.md).
## Example 1: Using an Impl struct
```cpp
struct PersonImpl {
rfl::Rename<"firstName", std::string> first_name;
rfl::Rename<"lastName", std::string> last_name;
int age;
};
class Person {
public:
// 1) Publicly define `ReflectionType`
using ReflectionType = PersonImpl;
// 2) Constructor that accepts your `ReflectionType`
Person(const PersonImpl& _impl): impl(_impl) {}
~Person() = default;
// 3) Method called `reflection` that returns `ReflectionType`
const ReflectionType& reflection() const { return impl; }
// ...add some more methods here...
private:
PersonImpl impl;
};
```
## Example 2: Matching variables, the safe way
`rfl::Field` is designed in a way that you have to explicitly initialize
every the field (using `rfl::default_value`, if necessary), otherwise
you will get a compile-time error.
A frequent error that happens during serialization/deserialization is that programmers
add a field to their class (`Person` in this example), but forget to update
their serialization routine.
The example as shown below will protect you from any such errors, as all
fields will have to be explicitly initialized, otherwise you will get a
compile-time error. If you add a new field to `Person` you will have to
add it to `PersonImpl` as well and then explicitly initialize it in the
constructor.
Don't worry `operator()` in `rfl::Field` is inlined. There won't be any
runtime overhead.
```cpp
struct PersonImpl {
rfl::Field<"firstName", std::string> first_name;
rfl::Field<"lastName", std::string> last_name;
rfl::Field<"age", int> age;
};
class Person {
public:
// 1) Publicly define `ReflectionType`
using ReflectionType = PersonImpl;
// 2) Constructor that accepts your `ReflectionType`
// This as the additional benefit that not only the types,
// but also the names of the fields will be checked at compile time.
Person(const PersonImpl& _impl): first_name(_impl.first_name),
last_name(_impl.last_name), age(_impl.age) {}
~Person() = default;
// 3) Method called `reflection` that returns `ReflectionType`
ReflectionType reflection() const {
return PersonImpl{
.first_name = first_name,
.last_name = last_name,
.age = age};
}
// ...add some more methods here...
private:
rfl::Field<"firstName", std::string> first_name;
rfl::Field<"lastName", std::string> last_name;
rfl::Field<"age", int> age;
};
```
## Example 3: Matching variables, the unsafe way
If, for any reason, you absolutely cannot change the fields
of your class, you have to make sure that all classes are properly
initialized or face runtime errors.
```cpp
struct PersonImpl {
// ... same as in Example 1 or 2
};
class Person {
// 1) Publicly define `ReflectionType`
using ReflectionType = PersonImpl;
// 2) Constructor that accepts your `ReflectionType`
Person(const PersonImpl& _impl): first_name(_impl.first_name()),
last_name(_impl.last_name()), age(_impl.age()) {}
// ... same as in Example 2
private:
std::string first_name;
std::string last_name;
int age;
};
```
|