This is just a summarizing readme providing the most vital information. The official documentation can be found at https://cppproperties.insane.engineer.
This is a C++20 library providing a property system to client classes.
The library is built with the following aspects in mind:
- Modern C++
- Easy to use
- Providing "raw access" to the properties just as if they were regular class members.
- Easy registration of custom property types.
- Easy integration of optional (de)serialization (XML & JSON already optionally built-in).
- Observer interface for property change notifications.
- Support for linked properties (properties in a base class not implementing this library).
- GUI generator (Qt widgets)
A couple of things to be aware of when using this library:
- Requires a C++20 capable compiler
- Properties are stored on the heap
- The memory layout of
struct { MAKE_PROPERTY(a, int) };
is not the same asstruct { int a; };
- Property change notification observer callbacks are invoked by which ever thread modified the property value.
This library is MIT licensed.
- If JSON (de)serialization is enabled, nlohmann::json is used for JSON serialization. The json library itself is MIT licensed.
- If XML (de)serialization is enabled, tinyxml2 is used for XML serialization. The tinyxml2 library itself is zlib licensed.
Any type can be registered as a property type using the REGISTER_PROPERTY
macro.
For convenience, a set of built-in types are already registered:
bool
int
float
double
std::basic_string<T>
(eg.std::string
,std::wstring
, ...)std::filesystem::path
If the cmake option CPPPROPERTIES_ENABLE_BOOST
is set to ON
, the following types are also built-in:
boost:uuids::uuid
If the cmake option CPPPROPERTIES_ENABLE_QT
is set to ON
, the following types are also built-in:
QString
QPoint
Start by reading the Usage
section below. More examples can be found in the examples directory.
Basic usage only requires inheriting from tct::properties::properties
and adding properties using MAKE_PROPERTY()
:
struct shape :
tct::properties::properties
{
MAKE_PROPERTY(x, float);
MAKE_PROPERTY(y, float);
};
The defined properties may now be used as if they were just class members of type float
:
int main(void)
{
shape s;
s.x = 24.48f;
s.y = -13.29f;
// Print them
std::cout << "s.x = " << s.x << std::endl;
std::cout << "s.y = " << s.y << std::endl;
}
Custom types may be used after registering them with REGISTER_PROPERTY()
:
/**
* Define a custom type 'color'.
*/
struct color
{
std::string name;
uint8_t r, g, b;
[[nodiscard]] std::string to_string() const
{
// ...
return { };
}
void from_string(const std::string& str)
{
// ...
}
};
/**
* Register the property
*/
REGISTER_PROPERTY(
color,
[this](){ return data.to_string(); },
[this](const std::string& str){ this->data.from_string(str); }
)
/**
* Client class using properties.
*/
struct shape :
tct::properties::properties
{
MAKE_PROPERTY(x, float);
MAKE_PROPERTY(y, float);
MAKE_PROPERTY(stroke_color color);
MAKE_PROPERTY(fill_color color);
};
Properties allow registering observers to notify them upon changes of the property value.
struct shape :
tct::properties::properties
{
MAKE_PROPERTY(x, float);
MAKE_PROPERTY(y, float);
shape()
{
x.register_observer([](){ std::cout << "x property changed!\n"; });
y.register_observer([](){ std::cout << "y property changed!\n"; });
}
};
int main()
{
shape s;
s.x = 42; // Prints "x property changed!";
s.y = 73; // Prints "y property changed!";
return 0;
}
The library comes with built-in support for (de)serialization. Classes can be easily (de)serialization to/from XML:
struct shape :
tct::properties::properties
{
MAKE_PROPERTY(x, float);
MAKE_PROPERTY(y, float);
}
int main(void)
{
// Create a shape
shape s;
s.x = 13;
s.y = 37;
// Serialize to std::string using XML format
const std::string& xml_string = s.to_xml();
std::cout << xml_string << std::endl;
// Serialize to XML file
s.to_xml_file("/path/to/shape.xml");
// Deserialize from std::string
shape s2;
s2.from_xml(xml_string);
// Deserialize from XML file
shape s3;
s3.from_xml_file("/path/to/shape.xml");
return 0;
}
One is likely to encounter a scenario where a client class derived
inherits from tct::properties::properties
but also from another, existing base class base
.
In this case serializing an instance of derived
will only contain the properties created with MAKE_PROPERTY
. However, one might like (or need) to also include members of the base
class although these members are not registered as properties in the base
class.
An example:
struct base
{
int x;
int y;
};
struct derived :
public base,
public tct::properties::properties
{
MAKE_PROPERTY(name, std::string);
};
Serializing instances of type derived
will contain the name
properties but not other vital information such as X & Y coordinates which are public members of base
. In this cae, LINK_PROPERTY()
may be used to include them in (de)serialization too:
struct base :
{
int x;
int y;
};
struct derived :
public base,
public tct::properties::properties
{
MAKE_PROPERTY(name, std::string);
LINK_PROPERTY(x, &x);
LINK_PROPERTY(y, &y);
};
This is similar to Linked properties
but instead of directly accessing a base class member we use the corresponding getter & setters. This way, members from a base class only accessible via getters & setters can be included in (de)serialization.
An example:
struct base
{
public:
void set_x(const int x) { m_x = x; }
[[nodiscard]] int x() const { return m_x; }
private:
int m_x = 0;
};
struct derived :
base,
tct::properties::properties
{
derived()
{
LINK_PROPERTY_FUNCTIONS(x, int, base::set_x, base::x)
}
};
If CPPPROPERTIES_ENABLE_QT_WIDGETS
is set to ON
, Qt based widgets can be generated automatically for a property or a property group:
#include <iostream>
#include <QApplication>
#include <QWidget>
#include "cppproperties/properties.hpp"
#include "cppproperties/qt_widgets/factory.hpp"
struct shape :
tct::properties::properties
{
MAKE_PROPERTY(enabled, bool);
MAKE_PROPERTY(x, int);
MAKE_PROPERTY(y, int);
shape()
{
enabled.register_observer([](){ std::cout << "enabled changed!\n"; });
x.register_observer([](){ std::cout << "x changed!\n"; });
y.register_observer([](){ std::cout << "x changed!\n"; });
}
};
struct circle :
shape
{
MAKE_PROPERTY(radius, int);
circle()
{
radius.register_observer([](){ std::cout << "radius channged!\n"; });
}
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
circle s;
// Set some property values
s.x = 24;
s.y = 48;
s.radius = 14;
// Create widget
auto w = tct::properties::qt_widgets::factory::build_form(s);
if (w)
w->show();
return a.exec();
}
This library provides a doctest based test suite under /test
. The corresponding cmake target is tests
.