Looking at this question, it reminds me to my own "Object with properties" concept. I consider it rather as pragmatical than elegant but I dare to present it:
To establish the dependency between Field
and Object
, I introduced a back-reference to Object
in Field
. At the cost of an additional member, this has certain conveniencies.
class Field – the base class required to store fields:
class Object; // forward reference
// super class of all object fields
class Field {
// variables:
private:
// parent object
Object &_obj;
// methods:
protected:
// constructor.
Field(Object &obj);
// destructor.
~Field();
// disabled:
Field(const Field&) = delete;
Field operator=(const Field&) = delete;
}
The corresponding class Object – the field container:
#include <vector>
// super class of objects which may contain fields
class Object {
// friends:
friend class Field;
// variables:
private:
// table of all registered fields
std::vector<Field*> _pFields;
// methods:
protected:
// constructor.
Object() = default;
// destructor.
virtual ~Object() = default;
// disabled:
Object(const Object&) = delete;
Object& operator=(const Object&) = delete;
};
The implementation of class Field
has to "know" both classes:
#include <algorithm>
// implementation of class Field
Field::Field(Object &obj): _obj(obj)
{
_obj._pFields.push_back(this);
}
Field::~Field()
{
_obj.erase(
std::find(
_obj._pField.begin(), _obj._pField.end(), this));
}
The template for field instances:
// a class template for derived fields
template <typename VALUE>
class FieldT: public Field {
// variables:
private:
// the value
VALUE _value;
// methods:
public:
// constructor.
FieldT(Object &obj, const VALUE &value):
Field(obj), _value(value)
{ }
// copy constructor.
FieldT(Object &obj, const FieldT &field):
Field(obj), _value(field._value)
{ }
// disabled:
FieldT(const FieldT&) = delete;
FieldT& operator=(const FieldT&) = delete;
};
The advantages and disadvantages as considered by me:
Pros:
The object would never register non-fields.
You may never forget to construct fields explicitly because the C++ compiler will remind you in this case. (Fields are not default constructable.)
You may decide in a copy constructor of a derived Object
whether to copy the field or initialize it with a constant (or alternative) value instead.
Cons:
It requires explicitly written code in every derived Object
which might be considered as tedious. (I, personally, like explicit code to a certain amount to make maintenance easier.)
It is not foolproof.
E.g. fields created with new
are "begging" for memory leaks. (Although... I never considered to simply delete the operator new()
. I've to check out this soon.)
So, how could this look in real-life:
// storage of a 2d vector
struct Vec2f {
float x, y;
Vec2f(float x, float y): x(x), y(y) { }
};
// storage of a 2d circle
class Circle: public Object {
// fields:
public:
// center
FieldT<Vec2f> center;
// radius
FieldT<float> radius;
// methods:
public:
// constructor.
explicit Circle(float x, float y, float r):
Object(),
center(*this, Vec2f(x, y)),
radius(*this, r)
{ }
// copy constructor.
Circle(const Circle &circle):
Object(),
center(*this, center),
radius(*this, radius)
{ }
};
Note:
You may wonder why the fields are public – this is intentional. In my real-life incarnation, these fields have a lot more i.e. getter and setter as well as (optional) signal slots to install guardians and modification notifier. My intention was to free me from the tedious writing of access methods in the objects.
In real-life this concept has even more potential:
It's sufficient for generic I/O classes which may write and read XML files or other formats.
It can be obtained in a second concept of generic GUI classes to simplify the creation of user interfaces in a generic way.
Worth of the costs for administrational extra data:
Some extra bytes seems often enough worth to me if they promise a lot of safed development time. Of course, there are counter examples where I consider every single bit e.g. the vertex data when loading geometry for visual simulation. (For the vertex data example, I never considered a property concept to access every single vertex. Instead, I used the properties to access arrays of vertices as once.)
Concerning the additional requirement of additional construction details:
class D{
int field1=5;
{ do something very custom; }
//^ have to be executed after "field1=5;" but before "field2=7"
int field2=7;
int field3=8;
{ do something very custom ; }//have to be executed after "field3=8;"
};
I would choose a simple solution which has nothing to do with the above concept:
class D {
int field1 = 5;
struct InitBefore {
InitBefore() {
// do something very custom;
}
} initBefore;
int field2 = 7;
int field3 = 8;
struct InitAfter {
InitAfter() {
// do something very custom;
}
} initAfter;
// the rest...
};
Unfortunately, this doesn't fulfill the extra requirement:
without wasting 1 char for each block.
(The reason can be found in SO: sizeof empty structure is 0 in C and 1 in C++ why?.)
May be, it could be solved exploiting the sequence operator and static methods applied to the constructor of the appropriate member. This even seems so ugly to me that I don't dare to sketch code for this.
May be, it could be solved to make an embedded, private derived class of the respective field where the "very custom code" can be added in the constructor. Due to the fact that fields actually have data the empty struct/class problem should be prevented.
Actually, I never cared about the non-zero struct
s/class
es because I needed this trick itself only in rare cases (where only a few resp. instances are expected).
As there are (at the time of writing) still 7 days until the bounty is over I will keep this in mind...
field1,2,3
real name areDatabaseHenToCage1to1
,DatabaseChickToFood1to1
. – Darondaroogestd::vector<C> regis = { C(), C(), C() };
EDIT: or make it a map, thus keeping the names readable... – Mcvayd->getField(D::ENUM_FIELD1);
It would work, but in some situation, it seems to be less suitable/flexible ... quite subjective. – Darondarooge