Question:
Very good. First of all, the question may not be formatted correctly. I will be editing it as I add more information or see that the comments show a lack of information or structure.
I have a structure made in POD, in C99.
I will give an example of what type of structure I usually find that I am interested in making a wrapper (kindly extracted from Freetype 2.4.4):
typedef enum BDF_PropertyType_
{
BDF_PROPERTY_TYPE_NONE = 0,
BDF_PROPERTY_TYPE_ATOM = 1,
BDF_PROPERTY_TYPE_INTEGER = 2,
BDF_PROPERTY_TYPE_CARDINAL = 3
} BDF_PropertyType;
typedef struct BDF_PropertyRec_
{
BDF_PropertyType type;
union {
const char* atom;
FT_Int32 integer;
FT_UInt32 cardinal;
} u;
} BDF_PropertyRec;
My question is: how can I make a wrapper in C++98 to interconnected structures?
I thought about making a class that uses a BDF_PropertyRec
, but using it as a private object and using it, but I still depend on malloc
and free
for its life cycle, quickly discarding the idea because I have to keep both objects in memory, as they do here: https: //os.mbed.com/questions/241/How-to-wrap-C-functions-into-C-class/
Here, https://stackoverflow.com/a/17454763/8607301 , they apply a form of inheritance to achieve a wrapper. I don't see how to achieve this with classic data or POD structures that have pointers connected to other structures, or a vector of such elements given by the base pointer and maxlength, something like this:
typedef struct t_Elementos_utilizados
{
uint8_t valor1;
uint16_t valor2;
uint8_t valor3;
uint32_t valor4;
} * Elementos_utilizados; // ¡ojito al elemento puntero, aquí hay trampa!
typedef struct t_Estructura_POD_con_vectores
{
uint32_t elementos;
Elementos_utilizados vector; // ¡Esto tiene trampa, lee bien la definición del typedef anterior!
} Estructura_POD_con_vectores; //< ¿Cómo enganchar esto en una clase?
In Software Engineering ( https://softwareengineering.stackexchange.com/q/181587/296879 ) they comment on using façades or std::make_unique
. In the case of C++98, std::make_unique
doesn't work (you'd have to insert the TR1 extension or use boost, and for now, that's not feasible), and a façade pattern adds too many layers to access elements of that façade pattern. vector, making encapsulation difficult (one layer for the first structure, another for the second, and a communication layer between them, leaving the original structure duplicated, but I don't see it being accessible without copies).
From what I've been reading in C++ Coding Standards – 101 Rules, Guidelines, and Best Practices, by Herb Sutter and Andrei Alexandrescu, it recommends using RAIIs (recommendation 13), which involves duplicating and having to maintain both objects, the structure object and the object object. Converted C++.
I repeat the question: How to make a wrapper in C++98 (obsolete!) to a structure connected to others in C99?
Answer:
There is no single way to make a wrapper. There are many aspects to take into account:
- Project needs.
- Available resources (time, money, personnel).
- State of the art (it is not the same to face a project from scratch as an existing one).
- Personal tastes of the development team.
As you have commented, the wrapper can be developed by inheritance. This solution is easy to apply:
class MyBDF_PropertyRec : public BDF_PropertyRec
{
public:
MyBDF_PropertyRec()
{
type = BDF_PROPERTY_TYPE_NONE;
}
void SetValue(char const* atom)
{
type = BDF_PROPERTY_TYPE_ATOM;
u.atom = atom;
}
void SetValue(int value)
{
type = BDF_PROPERTY_TYPE_INTEGER;
u.integer = value;
}
};
But its main drawback is that the structure of C is visible at all times, so its internal structure can be manipulated:
MyBDF_PropertyRec propertyRec;
propertyRec.type = BDF_PROPERTY_TYPE_INTEGER;
property.u.atom = "abcdef";
Instead of inheritance, you can opt for composition, but then the design can be complicated since this solution can force us to repeat functionality (this is not the case in this example):
class PropertyRec
{
BDF_PropertyRec data;
public:
PropertyRec()
{
data.type = BDF_PROPERTY_TYPE_NONE;
}
void SetValue(char const* atom)
{
data.type = BDF_PROPERTY_TYPE_ATOM;
data.u.atom = atom;
}
void SetValue(int value)
{
data.type = BDF_PROPERTY_TYPE_INTEGER;
data.u.integer = value;
}
BDF_PropertyRec GetBDFPropertyRec() const
{ return data; }
};
And these are just two options within the wide range of possibilities. Reaching extreme cases, a complete wrapper of the library could be created. In this case the C library would be completely hidden and the programmer would only see a C++ layer. This option is obviously the most expensive as it requires building the entire interface in C++.
class PropertyRec
{
std::variant<std::string,int,unsigned int> value;
public:
void SetValue(std::string const& atom)
{
value = atom;
}
void SetValue(int intValue)
{
value = intValue;
}
};
namespace BDFUtils
{
PropertyRec GetProperty(Face const& face, std::string const& propertyName)
{
BDF_PropertyRec property;
FT_Face ftFace = /* ... */
FT_Get_BDF_Property(face,
propertyName.c_str(),
&property );
PropertyRec toReturn;
switch( property.type )
{
case BDF_PROPERTY_TYPE_ATOM:
toReturn.SetValue(std::string(property.u.atom));
break;
case BDF_PROPERTY_TYPE_INTEGER:
toReturn.SetValue(property.u.integer);
break;
// ...
}
return propertyRec;
}
}