The Problem
I want to create a debugging application over my libraries for testing, debugging, ... purposes. But I don't want to give the end-user additional non-necessary APIs.
For example, consider an application that visualizes the program's plugin usage of the library to the end-user. So I can't use standard debuggers like GDB or LLDB with a Release build containing some debugging information. How could be the library/debugging application designed for that?
The Restrictions
Must work on Windows(MSVC-2015) and Linux(GCC-8).
C++11 only.
No debug information.
No core dumps.
No additional public APIs.
The Requirements(If It's Possible)
Accessing the library's main class private members without additional public/protected APIs I think could be enough if there is no other way to do it.
Possible Solution
I just introduce an additional symbol into my main library class as the class friend:
Complete code: MyLibrary.hh
class MyClassPrivate;
class MyClass {
public:
friend class MyClassDebugger;
int value() const;
private:
friend class MyClassPrivate;
std::unique_ptr<MyClassPrivate> impl_;
};
And then I export the class header and the class private header to the user and the user could just define the MyClassDebugger symbol and use it to access to the MyClass private implementations:
Complete code: main.cc
class MyClassDebugger {
public:
void modify(MyClass& object)
{
object.impl_->value = 100;
}
};
int main()
{
MyClass object;
MyClassDebugger().modify(object);
std::cout << object.value() << std::endl;
}
The complete code of the example: https://gist.github.com/gccore/397fb6147280bd32b6fe340aa6ce579a
I would consider dropping your “No additional public APIs” requirement, and implement some reflection interface for your objects, which allows users to list properties exposed to debug interface, get property values, and update these values. Here’s an example how that API may look like.
// Change this enum to contain types of various properties you have in your classes
// For instance, if you don't have nested objects, remove the corresponding entry from the enum
enum struct ePropertyType: uint8_t
{
Empty = 0,
Int32,
FP32,
String,
NestedObject
};
struct iDebugView;
// Variant structure for the values
struct sPropVariant
{
// The type of the value
ePropertyType type = ePropertyType::Empty;
// The value itself
union
{
int int32;
float fp32;
const char* string;
iDebugView* nestedObject;
};
};
// Describes a single field of some particular type
struct sPropertyDesc
{
std::string name;
ePropertyType type;
};
// Debugger interface to view and change private fields of objects
struct iDebugView
{
// Get descriptor for all properties exposed by this object.
// Note this doesn't include the values i.e. only depends on the object's type but not the instance
// Your implementation should return reference of a global variable, possibly lazily initialized on first use
virtual const std::vector<sPropertyDesc>& listProperties() const = 0;
// Get value of the property identified by 0-based index
// Returns ePropertyType::Empty if the index was out of range
virtual sPropVariant getValue( uint32_t index ) const = 0;
// Set value of the property identified by 0-based index.
// Returns false for errors such as index out of range, type mismatch, or trying to set NestedObject property
virtual bool setValue( uint32_t index, const sPropVariant& value ) = 0;
};
While not terribly complicated, that new API should enable GUI similar to PropertyGrid in C#, allowing to inspect and modify private properties of your classes.
Related
Plz check following c++ code: (nothing special, should be compliant to c++ 2nd edition from 1991)
class C
{
// also defines all the methods called in template method below with the obvious name and args.
public: template<typename TEnum> void SetNullableEnumValuePtr(CNullable<TEnum>* aEnumPtr)
{
if(sizeof(TEnum) == 4)
{
this->SetNullableUInt32Ptr(reinterpret_cast<CNullable<UInt32>*>(aEnumPtr));
}
else if (sizeof(TEnum) == 2)
{
this->SetNullableUInt16Ptr(reinterpret_cast<CNullable<UInt16>*>(aEnumPtr));
}
else if (sizeof(TEnum) == 1)
{
this->SetNullableUInt8Ptr(reinterpret_cast<CNullable<UInt8>*>(aEnumPtr));
}
else
{
this->FailEnumSize();
}
}
}
basic conditions
class CNullable follows the well known, basic nullable pattern implemented i.e. for c# in certain frameworks but could also be any template with one argument.
as the names imply, TEnum is used for different enum types. i.e. enum FooEnum { foo };
For different platforms/compilers it is true, that with for the same enum type it goes to different if branches: On Win32 all enums are compiled with a size of 4 bytes always. On my MCU it compiles to uint8, if the values of the enum field implies to do so.
1. Not my focus:
i'm not sure, if the c++ standard allows compliant compilers to generate enums with different sizes in respect of the required size but i think it is said, that it is compliant.
2. not my focus:
it is also obvious that a cleaner / more robust solution would generate explicit template instances for the function for ALL used enum types. however i might do this and hope the compiler/optimizer is smart/compliant enough not to generate extra code for all types.
3. Not my focus:
i know that reinterpret_cast can be evil but there is a reason the c++ standard defines it. so please stop downrating my questions just because you dont like it or dont understand it.
4. Not my focus:
i know there are certain websites, you can test code on different compilers.
5. would help too:
also if you have a better idea how to treat this coding issue you're welcome.
6. My focus:
i wanna focus this question on the issue, if the c++ standard [1] (or any other standard) defines rule(s), that the templates instances for intXX and uintXX (Where XX is the bit width and u indicates a unsigned version of the native integer) must forcefully compile to a code where a reinterpret_cast from CNullable<unsigned intXX> to CNullable<intXX> is guaranteed to run properly. it seems to be likely that a straight forward implementation of the tool chain may not cause problems in this case, but i got used to be carefull with such assumptions.
conclusion:
if one could give me the reference(s) to chapter inside the (c++) or other standard treading these issues (1 to 6) - that would be great.
thank you!
[1] Please understand the sad reality, that the platform i work on does even not have a ready compiler specification. so it is quite trial and error and i wanna understand the techniques behind to get a better feeling for it - because treading the issue deterministic seems not to be possible since the compiler reference does not give a clear statement what standard it supports: They stated an ETSI standard rather than an iso c++ standard and they didnt give enough info to find the documentation of the standard - no version number, no document number, no document name. when i search for the info given in the compiler documentation on the ETSI page i get > 24.000 results (!) XD
PS: Edit: I request the info:
a) Which standards / specifications ensure that this code runs properly?
b) Does anyone have a clean solution for an abstraction using different template instances for different enum types?
Ok think i i have a clean solution. It uses a wrapper pattern to hide the abstraction of concrete value types behind a virtual method.
I hope that is enough code to get the intention. I also put a very simple example of how i use it.
I guess in newer c++ version there are some stl elements which can replace the classes i wrote. But i need it for a very old c++ standard, which even isn't iso.
I really hope there are no errors since not all symbols are defined. but the essential stuff shall be shown in this example implementation.
// platform dependent UInt32 type.
typedef unsigned __int32 UInt32;
template<typename T>
class CNullable
{
public: bool HasValue;
public: T Value;
public: CNullable(): Value(), HasValue(false) {}
public: CNullable(const T& aValue): Value(aValue), HasValue(true) {}
public: template<typename T1>
CNullable<T1> To() const
{
return this->HasValue ? CNullable<T1>(static_cast<T1>(this->Value)) : CNullable<T1>();
}
CNullable<UInt32> ToU32n() const
{
return this->To<UInt32>();
}
};
// U32-Nullable type
typedef CNullable<UInt32> UInt32n;
// U32-Nullable argument type.
typedef const CNullable<UInt32>& UInt32na;
// Some kind of variant value that can store different types of values pointing into an (intended) statically allocated model
class CValue
{
// Report failure. Break debuger, raise exception, log error - whatever.
private: static void Fail(const char* aTextPtr);
// Sets the value in the (intended) statically allocated datamodel
// Simple example with a UInt32 as new value
public: void SetTargetValue(UInt32na rhs);
// Sets the value in the (intended) statically allocated datamodel
// May use a visitor pattern (GOF) for resolving assigned types, but thats not the topic here.
public: void SetTargetValue(const CValue& rhs);
// Ensures that object is not sealed an fails if so.
private: bool CheckNotSealed();
// Allows to change to sealed state to protect the object agains modification.
// protection for "as static as possible"-memory-design
public: void Seal();
// Base class for Wrappers
class CValueWrapper
{
public: virtual ~CValueWrapper() {}
// Converts the current value as an U32.
public: virtual UInt32n GetInterpretedU32n() { Fail("Data conversion not supported."); return UInt32n(); }
// converts the new value from an U32 and sets the value
public: virtual void SetInterpretedU32n(UInt32na aU32n) { Fail("Data conversion not supported."); }
};
// Wrappers Base class for any enum related type.
class CEnumWrapperBase : public CValueWrapper
{
public: virtual UInt32n GetU32n() const = 0;
public: virtual void SetU32n(UInt32na aU32n) const = 0;
public: virtual UInt32n GetInterpretedU32n() { return this->GetU32n(); }
public: virtual void SetInterpretedU32n(UInt32na aU32n) { this->SetU32n(aU32n); }
};
// Wrapper base class for values of type = Nullable<TEnums>
template<class TEnum> class CNullableEnumWrapper : public CEnumWrapperBase
{
private: CNullable<TEnum>* mNullableEnumPtr;
public: CNullableEnumWrapper(CNullable<TEnum>* aNullableEnumPtr)
:
mNullableEnumPtr(aNullableEnumPtr)
{
}
public: virtual UInt32n GetU32n() const
{
return this->mNullableEnumPtr ? this->mNullableEnumPtr->ToU32n() : UInt32n();
}
public: virtual void SetU32n(UInt32na aU32n) const
{
if (this->mNullableEnumPtr)
{
*this->mNullableEnumPtr = aU32n.To<TEnum>();
}
}
};
// Wrapper base class for values of type = Nullable<TEnums>
template<class TEnum> class CEnumWrapper : public CEnumWrapperBase
{
public: CEnumWrapper(TEnum* aEnumPtr)
:
mEnumPtr(aEnumPtr)
{
}
private: TEnum* mEnumPtr;
public: virtual UInt32n GetU32n() const
{
return this->mEnumPtr ? static_cast<UInt32>(*this->mEnumPtr) : UInt32n();
}
public: virtual void SetU32n(UInt32na aU32n) const
{
if (this->mEnumPtr
&& aU32n.HasValue)
{
*this->mEnumPtr = static_cast<TEnum>(aU32n.Value);
}
}
};
// Allows to lock instantian of wrapper objects.
// In my bare metal application all wrappers are created on application startup
// and stay allocated until the device is switched of (by disconnecting power)
// [ThreadStatic]
public: static bool InstanciateValueWrapperEnabled;
// Set pointer to enum value (intended to be allocated in a static model)
public: template<class TEnum> void SetEnumValuePtr(TEnum* aEnumPtr)
{
if (this->InstanciateValueWrapperEnabled)
{
if (this->CheckNotSealed())
{
this->SetValueWrapperPtr(new CEnumWrapper<TEnum>(aEnumPtr), true);
}
}
else
{
Fail("Invalid operation.");
}
}
// Set pointer to nullable<enum> value (intended to be allocated in a static model)
public: template<class TEnum> void SetNullableEnumValuePtr(CNullable<TEnum>* aEnumPtr)
{
if (this->InstanciateValueWrapperEnabled)
{
if (this->CheckNotSealed())
{
this->SetValueWrapperPtr(new CNullableEnumWrapper<TEnum>(aEnumPtr), true);
}
}
else
{
Fail("Invalid operation.");
}
}
// Sets the member var and data type to 'CValueWrapper' (may support some types natively without a wrapper object)
public: void SetValueWrapperPtr(CValueWrapper* aValueWrapperPtr, bool aOwning);
};
// Model Base Code
//////////////////////////////////////
/// Application specific code
enum FooEnum { FooEnum_Val1 };
// Reads data from StdIn, uart, or whatever.
UInt32 ReadU32();
// Process data, for example output calculated data to another hardware interface.
void Process(CValue** aValuePtrs);
// Simple example of how its being used.
// in real environment its likely to encapsulate a set of CValue objects
// in a ModelInstance-Object and build it by a ModelDefinition object parsing a model definiton language (mdl)
// or adapt generated code. etc. etc...
void main()
{
// Define the static model:
static FooEnum gFooEnum;
static CNullable<FooEnum> gNullableFooEnum;
// Define the values to access the static model
CValue aFooEnumVal;
CValue aNullableFooEnumVal;
// Begin of init:
CValue::InstanciateValueWrapperEnabled = true;
aFooEnumVal.SetEnumValuePtr(&gFooEnum);
aNullableFooEnumVal.SetNullableEnumValuePtr(&gNullableFooEnum);
CValue::InstanciateValueWrapperEnabled = false;
// End of init
// Create an array of values
const UInt32 aPropertyCount = 2;
CValue* aPropertyPtrs[aPropertyCount] =
{
&aFooEnumVal,
&aNullableFooEnumVal
};
for (UInt32 aIdx = 0; aIdx < aPropertyCount; ++aIdx)
{
aPropertyPtrs[aIdx]->Seal();
}
// Very simple and unsave data receiption loop.
while (true)
{
UInt32 aPropertyIdToSet = ReadU32(); // The property id to receive.
UInt32 aNewValHasValue = ReadU32(); // Wether the value is defined
UInt32 aNewValValue = ReadU32(); // The value
UInt32n aNewVal // Nullable for NewValue
= aNewValHasValue // if value is defined
? UInt32n(aNewValValue) // Create a nullable which has a value
: UInt32n() // Create a nullable which has no value
;
CValue* aValuePtr = aPropertyPtrs[aPropertyIdToSet]; // Get the value to receive.
aValuePtr->SetTargetValue(aNewVal); // Set the value to the static model
Process(aPropertyPtrs); // Process data newly received.
}
}
my question looks like a duplicate of about a million questions found on SO but I can't find a satisfying solution.
I'm currently working on a little import/export dll for a specific file format which is build in a block-like structure with different types of blocks referencing each other.
EDIT: Each block type has its own functionality and provides different information with different data types and so on. So they're not just giving aliases for their links. Sorry about that confusion, I broke them down to be as simple as possible to just describe the problem I have.
So every block of data has some information and holds a list of links to other blocks (which may be empty). Links are just 64bit values representing the absolute byte offset inside the file.
A typical case for inheritance: BaseBlock and some derived block types.
As every block can have some links I gave my base class an array of links and implemented a method that returns a link specified by an enum acting as a "named index":
typedef uint64_t LINK;
class BaseClass
{
public:
enum BlockLink;
LINK getLink(BlockLink link) {
if(m_link_count < link) {
return m_links[link];
}
return 0;
}
private:
LINK* m_links;
uint64_t m_link_count;
}
In every derived class I would then define this enum just like:
Class ABlock: public virtual BaseBlock
{
public:
enum BlockLink {
link_to_f_block = 0,
link_to_another_f_block = 1,
...
link_to_d_block = 5
}
}
Class BBlock: public virtual BaseBlock
{
public:
enum BlockLink {
link_to_nice_a_block = 0,
link_to_evil_h_block = 1,
...
link_to_strange_t_block = 5
}
}
That would make accessing a link elegant by just using:
ABlock* aBlock;
...
/* some stuff that initializes aBlock */
...
LINK linkToDBlock = aBlock->getLink(ABlock::link_to_d_block);
The user would see there is an enum for accessing links and he doesn't need to know the index or stuff, while I wouldn't need to implement a method for each link in every derived block to get a self explaining way to access them.
Unfortunately this doesn't work as these enums are not compatible. So doing this I'm getting something like
The argument of type "ABlock::BlockLink" is not compatible with parameter of type "BaseBlock::BlockLink"
So, is there an elegant way of doing something like that? Something like a "named access" or whatever you would call it?
If for example I have a builder set up so I can create objects like so:
Node node = NodeBuilder()
.withName(someName)
.withDescription(someDesc)
.withData(someData)
.build();
How can I make sure that all variables used to build the object have been set before the build method?
Eg:
Node node = NodeBuilder()
.withName(someName)
.build();
Isn't a useful node because the description and data haven't been set.
The reason I'm using the builder pattern is because without it, I'd need a lot of combination of constructors. For example the name and description can be set by taking a Field object, and the data can be set using a filename:
Node node = NodeBuilder()
.withField(someField) //Sets name and description
.withData(someData) //or withFile(filename)
.build(); //can be built as all variables are set
Otherwise 4 constructors would be needed (Field, Data), (Field, Filename), (Name, Description, Data), (Name, Description, Filename). Which gets much worse when more parameters are needed.
The reason for these "convenience" methods, is because multiple nodes have to be built, so it saves a lot of repeated lines like:
Node(modelField.name, modelField.description, Data(modelFile)),
Node(dateField.name, dateField.description, Data(dateFile)),
//etc
But there are some cases when a node needs to be built with data that isn't from a file, and/or the name and description are not based on a field. Also there may be multiple nodes that share the same values, so instead of:
Node(modelField, modelFilename, AlignLeft),
Node(dateField, someData, AlignLeft),
//Node(..., AlignLeft) etc
You can have:
LeftNode = NodeBuilder().with(AlignLeft);
LeftNode.withField(modelField).withFile(modelFilename).build(),
LeftNode.withField(dateField).withData(someData).build()
So I think my needs match the builder pattern pretty well, except for the ability to build incomplete objects. The normal recommendation of "put required parameters in the constructor and have the builder methods for the optional parameters" doesn't apply here for the reasons above.
The actual question: How can I make sure all the parameters have been set before build is called at compile time? I'm using C++11.
(At runtime I can just set a flag bits for each parameter and assert that all the flags are set in build)
Alternatively is there some other pattern to deal with a large number of combinations of constructors?
Disclaimer: This is just a quick shot, but I hope it gets you an idea of what you need.
If you want this to be a compiler time error, the compiler needs to know about the currently set parameters at every stage of the construction. You can achieve this by having a distinct type for every combination of currently set parameters.
template <unsigned CurrentSet>
class NodeBuilderTemplate
This makes the set parameters a part of the NodeBuilder type; CurrentSet is used as a bit field. Now you need a bit for every available parameter:
enum
{
Description = (1 << 0),
Name = (1 << 1),
Value = (1 << 2)
};
You start with a NodeBuilder that has no parameters set:
typedef NodeBuilderTemplate<0> NodeBuilder;
And every setter has to return a new NodeBuilder with the respective bit added to the bitfield:
NodeBuilderTemplate<CurrentSet | BuildBits::Description> withDescription(std::string description)
{
NodeBuilderTemplate nextBuilder = *this;
nextBuilder.m_description = std::move(description);
return nextBuilder;
}
Now you can use a static_assert in your build function to make sure CurrentSet shows a valid combination of set parameters:
Node build()
{
static_assert(
((CurrentSet & (BuildBits::Description | BuildBits::Name)) == (BuildBits::Description | BuildBits::Name)) ||
(CurrentSet & BuildBits::Value),
"build is not allowed yet"
);
// build a node
}
This will trigger a compile time error whenever someone tries to call build() on a NodeBuilder that is missing some parameters.
Running example: http://coliru.stacked-crooked.com/a/8ea8eeb7c359afc5
I ended up using templates to return different types and only have the build method on the final type. However it does make copies every time you set a parameter:
(using the code from Horstling, but modified to how I did it)
template<int flags = 0>
class NodeBuilder {
template<int anyflags>
friend class NodeBuilder;
enum Flags {
Description,
Name,
Value,
TotalFlags
};
public:
template<int anyflags>
NodeBuilder(const NodeBuilder<anyflags>& cpy) : m_buildingNode(cpy.m_buildingNode) {};
template<int pos>
using NextBuilder = NodeBuilder<flags | (1 << pos)>;
//The && at the end is import so you can't do b.withDescription() where b is a lvalue.
NextBuilder<Description> withDescription( string desc ) && {
m_buildingNode.description = desc;
return *this;
}
//other with* functions etc...
//needed so that if you store an incomplete builder in a variable,
//you can easily create a copy of it. This isn't really a problem
//unless you have optional values
NodeBuilder<flags> operator()() & {
return NodeBuilder<flags>(*this);
}
//Implicit cast from node builder to node, but only when building is complete
operator typename std::conditional<flags == (1 << TotalFlags) - 1, Node, void>::type() {
return m_buildingNode;
}
private:
Node m_buildingNode;
};
So for example:
NodeBuilder BaseNodeBuilder = NodeBuilder().withDescription(" hello world");
Node n1 = BaseNodeBuilder().withName("Foo"); //won't compile
Node n2 = BaseNodeBuilder().withValue("Bar").withName("Bob"); //will compile
Disclaimer: this is an idea. I'm not sure it even works. Just sharing.
You might try to:
remove build() method from NodeBuilder
regroup your mandatory fields into a single builder method of NodeBuilder, say NodeBuilder::withFieldData(bla, bli, blu) and/or NodeBuilder::withFieldData(structBliBlaBLU).
make withFieldData() to return a builder of a different type, say NodeBuilderFinal. Only this type of builder has build() method. You may inherit non-mandatory methods from NodeBuilder. (Strictly speaking, NodeBuilderFinal is a "Proxy" object)
This will enforce user to call withFieldData() before build(), while allowing to call other builder methods in arbitrary order. Any attempt to call build() on non-final builder will trigger compiler error. build() method will not show up in autocompletion until final builder is made ;).
If you don't want monolithic withFieldData method, you may return different proxies from each "field" method, like NodeBuilderWithName, NodeBuilderWithFile, and from those, you can return NodeBuilderWithNameAndFile, etc. until final builder will be built. This is quite hairy and will require many classes to be introduced to cover different orders of "field" calls. Similarly to what #ClaasBontus proposed in comments, you can probably generalize and simplify this with templates.
In theory, you may try to enforce more sophisticated constraints by introducing more proxy objects into the chain.
The only way I can imagine would be to have a number of static builder methods (or constructors) one for each set of required parameters that would return a builder instance, and then simple instance methods to set (or overwrite) parameters and that return the instance.
It will allow compile time checking, but at the price of a much more complex API, so I strongly advise you not to use it unless you really have good reasons to do.
This question can not be outdated. Let me share my solution to this problem.
class Car; //object of this class should be constructed
struct CarParams{
protected:
std::string name_;
std::string model_;
int numWheels_;
int color_;
struct Setter_model;
struct Setter_numWheels;
struct Setter_color;
public:
class Builder;
};
struct CarBuilder : CarParams{ //starts the construction
Setter_model& set_name(const std::string& name){
name_ = name;
return reinterpret_cast<Setter_model&>(*this);
}
};
struct CarParams::Setter_model : CarParams{
Setter_numWheels& set_model(const std::string& model){
model_ = model;
return reinterpret_cast<Setter_numWheels&>(*this);
}
};
struct CarParams::Setter_numWheels : CarParams{
Setter_color& set_numWheels(int numWheels){
numWheels_ = numWheels;
return reinterpret_cast<Setter_color&>(*this);
}
};
struct CarParams::Setter_color : CarParams{
Builder& set_color(int color){
color_ = color;
return reinterpret_cast<Builder&>(*this);
}
};
class CarParams::Builder : CarParams{
private:
//private functions
public:
Car* build();
// optional parameters
};
The class Car is defined bellow:
class Car{
private:
std::string name_;
std::string model_;
int numWheels_;
int color_;
public:
friend class CarParams::Builder;
//other functions
};
And build function in .cpp:
Car* CarParams::Builder::build(){
Car* obj = new Car;
obj->name_ = std::move(name_);
obj->model_ = std::move(model_);
obj->numWheels_ = numWheels_;
obj->color_ = color_;
return obj;
}
Maybe it is a little bit complicated, but looks nice on client side:
std::string name = "Name";
std::string model = "Model";
Car* newCar = CarBuilder()
.set_name(name)
.set_model(model)
.set_numWheels(3)
.set_color(0x00ffffff)
.build();
The error will occur in compile-time, if you miss something before build(). One more disadvantage is the strict order of arguments.
It can be combined with optional parameters.
Is there an idiomatic C++ way to dynamically associate properties with a fixed set of class instances?
Suppose for instance we have an Element class. Every element always has certain properties that are contained by member variables.
struct Element {
unsigned atomic_protons;
float mass;
};
There are other properties we might associate with each Element but not every program using the Element class will be interested in the same properties. Maybe sometimes we're interested in taste and sometimes we're interested in color and variables representing these properties might be expensive to initialize. Maybe we don't even know until runtime what properties we will want.
The solution that comes to my mind is a set of parallel arrays. One array contains the instances themselves and the indices of that array implicitly associate each instance with items in a series of ``parallel'' arrays.
// fixed set of Element instances
std::vector<Element> elements;
// dynamic properties
std::vector<Flavor> element_flavors;
std::vector<Color> element_colors;
Each vector of properties is created as necessary.
This solution is ok but does not at all resemble idiomatic C++. Besides aesthetics, this arrangement makes it awkward to look up a property from a given Element instance. We would need to insert an array index into each Element instance. Also, the size information in each vector is redundant.
It has the plus that if we're interested in all the values of a given property the data is arranged appropriately. Usually however, we want to go in the opposite direction.
Solutions that modify the Element class in some way are fine so long as the class need not be modified every time a new property is added. Assume also that there exist methods for working with the Element class that all programs share and we don't want those methods to break.
I think the std::unordered_map<Element*, Flavor> solution that PiotrNycz suggested is a perfectly "idomatic" way of associating a Flavor with a particular Element but I wanted to suggest an alternative.
Providing the operations you would like to perform on an Element are fixed you can extract out an interface:
class IElement {
public:
virtual ~IElement() {}
virtual void someOperation() = 0;
};
Then you can easily store a collection of IElement pointers (preferably smart pointers) and then specialize as you wish. With different specializations having different behavior and containing different properties. You could have a factory that decided which specialization to create at runtime:
std::unique_ptr<IElement>
elementFactory(unsigned protons, float mass, std::string flavor) {
if (!flavor.isEmpty()) // Create specialized Flavored Element
return std::make_unique<FlavoredElement>(protons, mass, std::move(flavor));
// Create other specializations...
return std::make_unique<Element>(protons, mass); // Create normal element
}
The problem in your case is you could easily get an explosion of specializations: Element, FlavoredElement, ColoredElement, FlavoredColoredElement, TexturedFlavoredElement, etc...
One pattern that is applicable in this case is the Decorator pattern. You make FlavoredElement a decorator that wraps an IElement but also implements the IElement interface. Then you can choose to add a flavor to an element at runtime:
class Element : public IElement {
private:
unsigned atomic_protons_;
float mass_;
public:
Element(unsigned protons, float mass) : atomic_protons_(protons), mass_(mass) {}
void someOperation() override { /* do normal thing Elements do... */ }
};
class FlavoredElement : public IElement {
private:
std::unique_ptr<IElement> element_;
std::string flavor_;
public:
FlavoredElement(std::unique_ptr<IElement> &&element, std::string flavor) :
element_(std::move(element)), flavor_(std::move(flavor)) {}
void someOperation() override {
// do special thing Flavored Elements do...
element_->someOperation();
}
};
class ColoredElement : public IElement {
private:
std::unique_ptr<IElement> element_;
std::string color_;
public:
ColoredElement(std::unique_ptr<IElement> &&element, std::string color) :
element_(std::move(element)), color_(std::move(color)) {}
void someOperation() override {
// do special thing Colored Elements do...
element_->someOperation();
}
};
int main() {
auto carbon = std::make_unique<Element>(6u, 12.0f);
auto polonium = std::make_unique<Element>(84u, 209.0f);
auto strawberry_polonium = std::make_unique<FlavoredElement>(std::move(polonium), "strawberry");
auto pink_strawberry_polonium = std::make_unique<ColoredElement>(std::move(strawberry_polonium), "pink");
std::vector<std::unique_ptr<IElement>> elements;
elements.push_back(std::move(carbon));
elements.push_back(std::move(pink_strawberry_polonium));
for (auto& element : elements)
element->someOperation();
}
So, there are two cases.
You can attach property to a program in a static way. But this property must be known before compilation. And yes, there is idiomatic way to do so. It is called specialization, derivation or inheritance:
struct ProgramASpecificElement : Element
{
int someNewProperty;
};
Second case is more interesting. When you want to add property at runtime. Then you can use map, like this:
std::unordered_map<Element*, int> elementNewProperties;
Element a;
elementNewProperties[&a] = 7;
cout << "New property of a is: " << elementNewProperties[&a];
IF you do not want to pay performance penalty for searching in a map, then you can predict in an Element that it might have new properties:
struct Property {
virtual ~Property() {}
};
template <typename T>
struct SimpleProperty : Property {
T value;
};
struct Elememt {
// fixed properties, i.e. member variables
// ,,,
std::unordered_map<std::string, Property*> runtimeProperties;
};
Element a;
a.runtimeProperties["age"] = new SimpleProperty<int>{ 7 };
cout << "Age: " << *dynamic_cast<SimpleProperty<int>*>(a.runtimeProperties["age"]);
OF course the code above is without any necessary validations and encapsulations - just a few examples.
For logging purposes, I would like to adapt various classes (for this reason I'd like a generic approach) to a key value dictionary : this could be seen as "key value serialization".
Let's assume the keys are pre-defined and that, depending on the input class we do want to adapt, each value may correspond to a specific attribute.
Values can always be encapsulated into an std::string.
This would be my approach :
Create an adapter class which can be dumped into the database
#include <keys.h> // enum with possible keys, defining type Key_t
namespace generic
{
class Adapter
{
public:
Adapter();
virtual ~Adapter();
virtual void init() = 0;
private:
std::map<Key_t, std::string> _data;
}
}
For every possible client, specialize the adapter class in its namespace, supposing it is friend with any client's specific business object model (to access attributes easily), and that it receives the instances of such models via const references in its constructor
e.g.
#include <generic/Adapter.h>
#include <client1/bom1.h>
#include <client1/bom2.h>
...
#include <client1/bomN.h>
namespace client1
{
class Adapter : public generic::Adapter
{
public:
Adapter(const Bom1& bom1,
const Bom2& bom2,
const BomN& bomN)
: _bom1(bom1), _bom2(bom2), _bomN(bomN)
{}
void init()
{
// Explicit data mapping in here
_map[NAME] = _bom1._name;
_map[TITLE] = _bom2._title;
....
....
}
private:
Bom1 _bom1;
Bom2 _bom2;
BomN _bomN;
}
}
What do you think about this approach ?
Is there a more generic way of achieving this in c++ ?
What would have been your design ?
Thanks!
Update
When a new client is implemented the logging engine shouldn't change: that is why the adapting logic should be distributed on client side rather than being implemented in the core of the logging engine.
The logging engine would be updated only if new keys are required (this would probably imply a database structural change).
I would have stored serialized strings for both keys and values.
Here I'm using the ldbSerialize method which uses boost serialization by default and can be easily specialized without creating a new class. For every new type of the key one would simply add a new specalization:
template <> inline void ldbSerialize<Key32> (string& bytes, const Key32& key) {
bytes += key.whatever();
}