I am updating an old piece of C++ code and am stuck on a design issue and need advice on the best course of action. The code handles geometric data. Currently, the code defines many global constants to handle element types:
#define TETRAHEDRON 0
#define HEXAHEDRON 1
Each constant has information associated with it that remains constant and which is currently handled by a class, in our case Topology.
int Topology::nodesPerElement(int topType)
{
switch(topType) {
case TETRAHEDRON:
return 4;
break;
case HEXAHEDRON:
return 8;
break;
}
}
The Topology class has many of these functions that simply switch on the global constant to figure out associated information. There are a lot of element types and many bugs are introduced by switch statements that don't consider all element types. If an element type is added all of these methods need to be fixed. I need a better way of doing this that keeps the associated information with the type.
Enumerations are an improvement over this design, but it doesn't solve the problem of associating data with the enumeration.
For simplicity, I would like to avoid needing to instantiate classes for each type, as each will contain only static data that doesn't change.
What I really need is a "static class" that holds this information and performs like the pseudocode below:
class Tetrahedron : public TopType {
static const int nodesPerElement = 4;
static const std::string name = "Tet";
etc...
}
Each method in Topology becomes trivial:
int Topology::nodesPerElement(TopType topType)
{
return topType.nodesPerElement;
}
Is there a way to do this in C++? I've thought about just getting rid of the enumerations and having separate child Topology classes for each TopologyType, but the feedback I get from others is that it's too complicated of a solution. I hope that my question is clear enough.
Create a base class that contains all of the properties that your objects should support, and a private constructor to set those properties. You don't need derived classes, then: you can use static public objects to create the objects that you want with the desired properties.
class TopologyObject
{
private:
int numberVertices;
int numberFaces;
// etc.
public:
int getVertices() { return numberVertices; };
int getFaces() { return numberFaces; };
protected:
TopologyObject(int vertices, int faces) :
numberVertices(vertices),
numberFaces(faces)
{};
public:
static TopologyObject Tetrahedron = new TopologyObject(4, 4);
// etc.
}
You can access the Tetrahedron with all of its properties via TopologyObject::Tetrahedron.
If you decide that you need more complex variable behavior based on the type of object, then you really do need derived classes and virtual methods for the overrideable behavior.
Unless your Topology types have different runtime behaviors (like drawing themselves), then I agree with your peers that sub-classing is overkill. Reporting static properties like nodesPerElement and name is hardly a runtime behavior.
Unless you are not telling us the whole story about Topology, it seems that what you need is a simple property map. Use std::map to associate a topology type code with a structure of topology properties. This refactoring resembles Replace Subclass with Fields.
Here's some code that may serve as inspiration:
#include <cassert>
#include <iostream>
#include <map>
#include <string>
struct Topology
{
enum Code {tetrahedron, hexahedron};
int nodesPerElement;
std::string name;
};
namespace // Anonymous namespace
{
// Lookup table associating topology code with properties
const struct {Topology::Code code; Topology topo;} topoTable_[] =
{
{Topology::tetrahedron, {4, "Tetrahedron"}},
{Topology::hexahedron, {6, "Hexahedron"}}
};
};
class TopologyMap // Singleton
{
public:
static TopologyMap lookup(Topology::Code code)
{
return Topology(instance().doLookup(code));
}
private:
typedef std::map<Topology::Code, Topology> Map;
Map map_;
TopologyMap()
{
// Initialize map with constant property table
size_t tableSize = sizeof(topoTable_) / sizeof(topoTable_[0]);
for (size_t row=0; row<tableSize; ++row)
{
map_[topoTable_[row].code] = topoTable_[row].topo;
}
}
static TopologyMap& instance()
{
static TopologyMap instance;
return instance;
}
const Topology& doLookup(Topology::Code code) const
{
Map::const_iterator match = map_.find(code);
assert(match != map_.end());
return match->second;
}
};
class Shape
{
public:
Shape(Topology::Code topoCode)
: topo_(TopologyMap::lookup(topoCode)) {}
const Topology& topology() const {return topo_;}
// etc...
private:
Topology topo_;
};
int main()
{
Shape shape1(Topology::tetrahedron);
Shape shape2(Topology::hexahedron);
std::cout << "shape1 is a " << shape1.topology().name << " with " <<
shape1.topology().nodesPerElement << " nodes per element.\n";
std::cout << "shape2 is a " << shape2.topology().name << " with " <<
shape2.topology().nodesPerElement << " nodes per element.\n";
};
Output:
shape1 is a Tetrahedron with 4 nodes per element.
shape2 is a Hexahedron with 6 nodes per element.
If the topology code is zero-based and continuous, then you may use simple array indexing instead of a map. However, array indexing will be more error-prone if someone messes around with the topology code enum. Here is the same example that uses array indexing:
#include <cassert>
#include <iostream>
#include <map>
#include <string>
struct Topology
{
enum Code {tetrahedron, hexahedron, CODE_COUNT};
int nodesPerElement;
std::string name;
};
namespace // Anonymous namespace
{
// Lookup table associating topology code with properties
const Topology topoTable_[] =
{
{4, "Tetrahedron"},
{6, "Hexahedron"}
};
};
class TopologyMap // Singleton
{
public:
static Topology lookup(Topology::Code code)
{
assert(code < Topology::CODE_COUNT);
return topoTable_[code];
}
private:
TopologyMap() {} // Non-instantiable
};
class Shape
{
public:
Shape(Topology::Code topoCode)
: topo_(TopologyMap::lookup(topoCode)) {}
const Topology& topology() const {return topo_;}
// etc...
private:
Topology topo_;
};
int main()
{
Shape shape1(Topology::tetrahedron);
Shape shape2(Topology::hexahedron);
std::cout << "shape1 is a " << shape1.topology().name << " with " <<
shape1.topology().nodesPerElement << " nodes per element.\n";
std::cout << "shape2 is a " << shape2.topology().name << " with " <<
shape2.topology().nodesPerElement << " nodes per element.\n";
};
Note that because the details of storing and retrieving Topology was encapsulated in TopologyMap, I didn't have to rewrite any code in Shape and main.
You can have classes with nothing but static member variables. And that's a nice way to encapsulate attribute data.
If you'd rather not do that, traits might get you what you want.
I'm not sure who advised you to avoid derived classes for each Toplogy type. To my eye, this problem is screaming for derived classes.
Unless you would need a very large number of such classes.
Personally I think the best way to store this information would be to create a general Shape class. Then, instead of coding all those static variables put them in a file/database and load your shape information from the data store when you start your program.
Couldn't you use a record to do this if your goal is to avoid class instantiation?
Really though, you should class the poop out of this.
If topType is contiguous and starting a 0, you could just maintain an array of structs and index into that, instead of trying to have classes and subclasses. This way the only code change you would need is to
add the struct: Easy
add an array of structs: Easy
change each method to index into array and return proper field of struct: Tedious, but you have to do this anyway.
It your TopologyType can just be modelled as an instance of a struct (i.e no methods on it etc), Classes + Derived classes is overkill, IMO.
Since (apparently) all the relevant data is available at compile time, one possibility would be to use an enumeration along with templates and specialization to do the job:
enum { tetrahedron, hexahedron };
template <int type>
struct nodes_per_element { int operator()() const {
throw std::invalid_argument("Attempt to use unknown shape");
};
template <>
struct nodes_per_element<tetrahedron> { int operator()() const { return 4; } };
template <>
struct nodes_per_element<hexahedron> { int operator()() const { return 8; } };
You'd use this like: int x = nodes_per_element<hexahedron>()(); If you try to use it for a value for which there's no specialization, that will invoke the un-specialized template, which will throw an exception, halting the program and (normally) displaying a message saying you attempted to use an unknown shape. Of course, you can customize how that's displayed (if at all).
This should quickly show where you have problems due to values that haven't been defined.
The other obvious possibility would be to just define a struct for each shape you're going to use, and create an array of those structs, using the name of the shape as an index into the data, and the name of the specific data you want will be the member of the struct. For just the nodes per element you've given, that would look like:
struct shape_data {
int nodes_per_element;
std::string name;
};
shape_data data[] = {
{4, "Tetrahedron"},
{8, "Hexahedron" }
};
Retrieving data would be something like:
shape_data &s = data[hexahedron];
std::cout << "A " << s.name << " has " << s.nodes_per_element << "nodes per element.\n";
Having look at the previous answers, I've decided to add my own.
To me there are 2 things that I would require of such a design:
the ability to define a new item without recompiling the whole program
the ability to look up an item based on a property (like the number of faces)
This can be quite easy to do, so here is my little bit of code:
class Solid
{
typedef std::vector<Solid> solids_type;
public:
Solid(std::string name, size_t faces, size_t nodes):
mName(name), mFaces(faces), mNodes(nodes)
{
}
///
/// Properties
///
const std::string& getName() const { return mName; }
size_t getFaces() const { return mFaces; }
size_t getNodes() const { return mNodes; }
///
/// Collection Handling
///
static bool Add(Solid solid); // only add if it's not already there.
///
/// struct Predicate: std::unary_function<Solid,bool>
///
template <class Predicate>
static const Solid* Get(Predicate pred)
{
solids_type::const_iterator it =
std::find_if(Solids().begin(), Solids().end(), pred);
return it == Solids().end()) ? 0 : &(*it);
} // Get
///
/// Some Predicates
///
class ByName: std::unary_function<Solid,bool>
{
public:
ByName(std::string name): mName(name) {}
bool operator()(const Solid& s) const { return s.getName() == mName; }
private:
std::string mName;
};
class ByFaces; /// ...
class ByNodes; /// ...
private:
/// Properties
std::string mName;
size_t mFaces;
size_t mNodes;
/// Collection
static solids_type& Solids()
{
static solids_type MSolids;
return MSolids;
}
}; // class Solid
And thus, now we can have:
// in tetrahedron.cpp
namespace
{
bool gTetrahedron = Solid::Add(Solid("Tetrahedron", 4, 4));
}
// in main.cpp
int main(int argc, char* argv[])
{
const Solid* myTetra = Solid::Get(Solid::ByFaces(4));
assert(myTetra->getName() == "Tetrahedron");
assert(myTetra->getFaces() == 4);
assert(myTetra->getNodes() == 4);
return 0;
} // main
And now we have met our goals:
Adding one new solid does not cause any recompilation
We can lookup solid based on their properties
We could also imagine:
being able to iterate through all the registered solids
having them sorted by number of faces, or whatever
defining a little macro for the registration
This is precisely what virtual functions are for. The classical way to do it would be:
class Topology
{
public:
virtual int nodesPerElement() const = 0;
// etc
};
class Tetrahedrom : public Topology
{
public:
virtual nodesPerElement() const { return 4; }
// etc
}
// etc
But if you really have an aversion to re-implementing the accessor methods (as opposed to just defining variables) you could do the following with templates (although it's really no less verbose):
class Topology
{
public:
virtual int nodesPerElement() const = 0;
// etc
};
template<typename T>
class ConcreteTopology : public Topology
{
public:
virtual int nodesPerElement() const { return T::nodesPerElement; }
// etc
};
struct Tetrahedron_Data {
int nodesPerElement = 4;
// etc
};
typedef ConcreteTypology<Tetraheadron_Data> Tetrahedron;
// etc
Related
I have an application where a specific message type needs to be selected based on user input (a string or int). There is a 1:1 mapping of a (string or int to a fixed type). For example 1-> int or "Apple"-> struct Apple ..and so on.
I tried to map int to a type using a template GetType<>.
struct messageA {int x;};
struct messageB {double y;};
/// Trying to map int to a type
template<int>
struct GetType{};
template<>
struct GetType<1> {
using type = messageA;
};
template<>
struct GetType<2> {
using type = messageB;
};
///
/// this some_task function is called from some UI
void some_task(int ID) {
// here I can not use the ID value in GetType< >
typename GetType<1>::type *msg = new typename GetType<1>::type();
some_other_generic_function(msg);
}
But it seems I can not use this, inside the function some_task because ID is a runtime argument.
The switch case is an option but then every time when a new message type is needed, I need to change the switch case.
What other ways can we achieve the association from int to type? Can we create some sort of list of types and which can be indexed with int (actually I needed string, but I will deal with strings later)?
Creating a type, based on some precondition, is where Creational Design Patterns are great. For this specific application I would propose a Factory method. This does mean you require a messageBase type class.
Using a base class, it becomes easy to map your type to a specific implementation using a std::map. However, you will need to expose shared properties through the base class.
struct messageBase {
virtual std::string print() = 0;
};
struct messageA : public messageBase {
int x{123};
std::string print() override { return std::to_string(x); }
};
struct messageB : public messageBase {
double y{123.321};
std::string print() override { return std::to_string(y); }
};
I find that the mapping part is better to do with a std::map than a switch due to the ease of extention.
// Lookup for ints + str
std::map<int, std::function<messageBase*()>> int_lookup;
std::map<std::string, std::function<messageBase*()>> str_lookup;
// Using a map instead of a switch - Put this in a factory
int_lookup[1] = []() { return new messageA(); };
int_lookup[2] = []() { return new messageB(); };
str_lookup["foo"] = []() { return new messageA(); };
str_lookup["bar"] = []() { return new messageB(); };
Example usage and a working example:
// Use the maps to create the "messages" on the fly
std::cout << int_lookup[1]()->print() << std::endl;
std::cout << int_lookup[2]()->print() << std::endl;
std::cout << str_lookup["foo"]()->print() << std::endl;
std::cout << str_lookup["bar"]()->print() << std::endl;
// Output:
// 123
// 123.321000
// 123
// 123.321000
I have a virtual / parent class
and many children of this class.
I assume that these children are a different way of generating answers to questions - such as various communication protocols, with one API.
They can have different versions.
class A {
public: virtual const char * [] GetProtocolName () {return "A"; }
};
class B: public A {
public: virtual const char * [] GetProtocolName () {return "B"; }
};
class C: public B {
public: virtual const char * [] GetProtocolName () {return "C"; }
};
....
Let's assume that in the program I want to list my child class / children list - each class has a function:
* char [] GetProtocolName ()
and on the basis of the protocol:
protocol 1
protocol 2
protocol x...
the user can choose by which class the communication should be handled
my question is as follows:
how in the program - ie after the compilation, and how to save it in the code, before compilation - I can determine that the selected class will be the child X of my virtual class / parent - based on text settings (SELECT USER in this list classes).
The problem is 2 things:
how to list each available class as a child of class A, which are available in the program
how to assign a child - choose one protocol from many - based on what you choose from the list (ie on the basis of * char [])
?
class * communicationProtocol = ?????
I'm brand new in the subject. Thank you for any hint. I do not know what phrase to use, and the phrases I want give me the knowledge I already have.
O! I found something like here:
namespace Ez {
std::shared_ptr<EzGraver> create(QString const& portName, int protocol) {
qDebug() << "instantiating EzGraver on port" << portName << "with protocol version" << protocol;
std::shared_ptr<QSerialPort> serial{new QSerialPort(portName)};
serial->setBaudRate(QSerialPort::Baud57600, QSerialPort::AllDirections);
serial->setParity(QSerialPort::Parity::NoParity);
serial->setDataBits(QSerialPort::DataBits::Data8);
serial->setStopBits(QSerialPort::StopBits::OneStop);
if(!serial->open(QIODevice::ReadWrite)) {
qDebug() << "failed to establish a connection on port" << portName;
qDebug() << serial->errorString();
throw std::runtime_error{QString{"failed to connect to port %1 (%2)"}.arg(portName, serial->errorString()).toStdString()};
}
switch(protocol) {
case 1:
return std::make_shared<EzGraverV1>(serial);
case 2:
return std::make_shared<EzGraverV2>(serial);
case 3:
return std::make_shared<EzGraverV3>(serial);
default:
throw std::invalid_argument{QString{"unsupported protocol '%1' selected"}.arg(protocol).toStdString()};
}
}
#ifndef EZGRAVERV1_H
#define EZGRAVERV1_H
#include <QSerialPort>
#include <memory>
#include "ezgraver.h"
namespace Ez {
struct EzGraverV1 : EzGraver {
using EzGraver::EzGraver;
/*! Moves the engraver up. */
void up() override;
...
on
https://github.com/camrein/EzGraver/blob/master/EzGraverCore/factory.cpp
it's almost what I was looking for ... i.e. it does not show the index and i understand that i need to specify this index, but i can see a construction on how to create several protocols :)
thank you for all the answers!
You could use a mix of constexpr function and std::conditional.
Add a getter function to your classes that will return the class type (or name if you want to):
constexpr static const char *getType()
{
return 0; // You should use enum here
}
Then add a constexpr function that will check for this value (using a switch case for example):
constexpr bool isType()
{
return Test::getType() == 0; // Do a real comparison with a templated function for example
}
Then use std::conditional to get your type:
typedef std::conditional<isType(), int, double>::type Type1;
This code is not to be used like that, but as an example to help you understand what I mean
A common approach is to use a "registry". You define a map from the name (that you could ask to the user or read from a file) and a function that creates the appropriate child.
The following is a toy but complete example; note how main doesn't need to know the list of all possible derived classes and still can create an object of a given derived class type from its name:
#include <iostream>
#include <map>
#include <string>
struct Shape {
virtual void draw() = 0;
virtual ~Shape() {};
};
struct Triangle : Shape {
virtual void draw() override { std::cout << "I'm a triangle!\n"; }
};
struct Circle : Shape {
virtual void draw() override { std::cout << "I'm a circle!\n"; }
};
std::map<std::string, Shape *(*)()> registry{
{"triangle", []()->Shape* { return new Triangle; }},
{"circle", []()->Shape* { return new Circle; }}
};
int main(int argc, const char *argv[]) {
if (argc != 2) {
std::cout << "You need to choose the shape type:\n";
for (auto& i : registry) {
std::cout << " " << i.first << "\n";
}
} else {
auto i = registry.find(argv[1]);
if (i == registry.end()) {
std::cout << "Unknown shape type\n";
} else {
Shape *s = i->second();
s->draw();
delete s;
}
}
return 0;
}
One subtle detail you should pay attention to is that if the derived classes are defined in other compilation units and their registration is also done during static initialization of those units there is formally the risk that the linker will not consider those classes at all unless there is some explicit reference to the units. In other words, suppose you have:
/// File a.cpp
#include "base.h"
#include "registry.h"
struct DerivedA : Base {
...
};
static int init_a = [](){
registry["a"] = []()->Base* { return new DerivedA; };
return 1;
}();
and similar files for all other derived classes.
Then the C++ standard says that it is possible that those classes will not be registered even if you compile them and link them in the program.
This happens because init_a initialization can be delayed until the first access to any object in the compilation unit is done. But if init_a is not initialized then there's no way someone can access this class because the name will not be in the registry. There's no guarantee that dynamic initialization of static duration objects is performed before the start of main, you're only guaranteed that it is performed before any use of any symbol in that compilation unit.
Unfortunately there is no portable atstart() in C++.
In other words using self-registering modules (i.e. no reference to any symbol in those compilation units is present anywhere else in the program) is not portable C++. It may work but there's no such a guarantee.
To be safe you should just register all the classes explicitly.
I have a (parent) class named Alma with the (virtual) function Getwidth() and two derived class of Alma, named Birs (with the special function Getheight()) and Citrom (with the special function Getdepth()). I want to declare an object - named Attila - which type is Birs or Citrom depending on a bool. Later, I want to use the common function Getwidth() and also the special functions (depending the bool mentioned).
My (not working) code:
/*...*/
/*Classes*/
class Alma{
public: virtual int Getwidth() = 0;
/*ect...*/
}
class Birs: public Alma{
int Getwidth(){return 1;}
public: int Getheight(){return 2;}
/*ect...*/
}
class Citrom: public Alma{
int Getwidth(){return 3;}
public: int Getdepth(){return 4;}
/*ect...*/
}
/*...*/
/*Using them*/
void Useobjects(){
/*Create object depending on bool*/
if(b00lvar){
Birs Andor();
std::cout<<Andor.Getwidth()<<" "<<Andor.Getheight()<<std::endl;
}else{
Citrom Andor();
std::cout<<Andor.Getwidth()<<" "<<Andor.Getdepth()<<std::endl;
}
/*Using the common part of object*/
std::cout<<Andor.Getwidth()<<std::endl;
/*Using the special part of object*/
if(b00lvar){
std::cout<<Andor.Getheight()<<std::endl;
}else{
std::cout<<Andor.Getdepth()<<std::endl;
}
/*ect...*/
}
This is a classic case of polymorphic object handling. Just make sure you are familiar with that concept as well with pointers and references.
What you need is something looking like:
Alma* Andor;
if(b00lvar){
Andor = new Birs();
std::cout<<Andor->Getwidth()<<" "<<Andor->Getheight()<<std::endl;
}else{
Andor = new Citrom();
std::cout<<Andor->Getwidth()<<" "<<Andor->Getdepth()<<std::endl;
}
Next use dynamic_cast to get back to the derived types and finally of course do not forget to delete the object. But first read about those concepts.
You cannot define a single object whose type is this or that, depending on something else. C++ doesn't work this way. C++ is a statically-typed language. This means that the type of every object is determined at compile time. Other languages, like Perl, or Javascript, are dynamically-typed, where the type of an object is determined at runtime, and a single object can be one thing, at one point, and something else at a different point.
But C++ does not work this way.
To do something like what you're trying to do, you have to refactor the code, and work with the virtual superclass. Something like this:
void UseObject(Alma &andor)
{
/*Using the common part of object*/
std::cout<<andor.Getwidth()<<std::endl;
/*Using the special part of object*/
/* This part is your homework assignment */
}
void Useobjects(){
/*Create object depending on bool*/
if(b00lvar){
Birs andor;
std::cout<<Andor.Getwidth()<<" "<<Andor.Getheight()<<std::endl;
UseObject(andor);
}else{
Citrom andor;
std::cout<<Andor.Getwidth()<<" "<<Andor.Getdepth()<<std::endl;
UseObject(andor);
}
}
Another approach would be to use two pointers, in this case passing two pointers to UseObject(). One of the two pointers will always be a nullptr, and the other one a pointer to the instantiated object, with UseObject() coded to deal with whatever object is passed in.
That's also possible, but will result in ugly code, and if I was an instructor teaching C++, I would mark down anyone who handed in code that did that.
If the type of the object (Alma or Citrom) is decided at the startup, then it's a classic polymorphism, as other answers described:
https://stackoverflow.com/a/36218884/185881
What're you missing from your design is, to name the common ancestor with common behaviors (e.g. Gyumolcs).
If the object should once act as Alma and other times as Citrom, you should implement a single class, which have a flag or enum (ACT_AS_CITROM, ACT_AS_ALMA), or, if the behavior is limited to one method, then it should have a parameter, which tells which action to perform (alma-like or citrom-like).
You can do this with pointer semantic and type introspection with dynamic_cast. I extended your example to show how I would approach it.
Here is the Demo
#include <iostream>
#include <memory>
using namespace std;
class Alma{
public:
virtual int Getwidth() = 0;
};
class Birs: public Alma{
public:
int Getwidth() { return 1; }
int Getheight() { return 2; }
};
class Citrom: public Alma{
public:
int Getwidth() { return 3; }
int Getdepth() { return 4; }
};
shared_ptr<Alma> make_attila(bool birs)
{
if (birs)
return make_shared<Birs>();
else
return make_shared<Citrom>();
}
void test_attila(shared_ptr<Alma> attila)
{
cout << "width: " << attila->Getwidth() << "\n";
if (auto as_birs = dynamic_pointer_cast<Birs>(attila))
cout << "height: " << as_birs->Getheight() << "\n";
else if (auto as_citrom = dynamic_pointer_cast<Citrom>(attila))
cout << "depth: " << as_citrom->Getdepth() << "\n";
}
int main() {
shared_ptr<Alma> attila = make_attila(true);
test_attila(attila);
attila = make_attila(false);
test_attila(attila);
return 0;
}
Next step would be to make make_attila a template function taking the Derived class as a template parameter instead of a bool.
template <class Derived>
shared_ptr<Alma> make_attila()
{
return make_shared<Derived>();
}
Two things:
If you want to use it outside the if, you will have to declare it outside the if.
You need references or pointers for this kind of polymorphism.
unique_ptr<Alma> Andor;
if (b00lvar) {
Andor = make_unique<Birs>();
} else {
Andor = make_unique<Citrom>();
}
std::cout << Andor->Getwidth() << std::endl;
Some other answer suggested using shared_ptr but that's overkill here. 99% of the time unique_ptr is sufficient.
Polymorphism isn't always the way to go if an object is known to be either a B or a C. In this case, a boost::variant is often more succinct.
Having said this, if you want to go down the polymorphic route it's important to remember something that will guide the design.
Polymorphic means runtime polymorphic. I.e. the program cannot know the real type of the object. It also cannot know the full set of possible types the object could be, since another developer could manufacture a type that your module's code knows nothing about. Furthermore, when using the Alma interface, the code should not need to know anything more. Invoking magic such as "I know it'll be a Citrom because the bool is true" is laying the foundations for a code maintenance nightmare a few weeks or months down the line. When done in commercial, production code, it results in expensive and embarrassing bug-hunts. Don't do that.
This argues that all relevant information about any object of type Alma must be available in the Alma interface.
In our case, the relevant information is whether it has the concept of height and/or depth.
In this case, we should probably include these properties in the base interface plus provide functions so that the program can query whether the property is valid before using it.
Here is something like your example written this way:
#include <iostream>
#include <memory>
#include <typeinfo>
#include <string>
#include <exception>
#include <stdexcept>
// separating out these optional properties will help me to reduce clutter in Alma
struct HeightProperty
{
bool hasHeight() const { return impl_hasHeight(); }
int getHeight() const { return impl_getHeight(); }
private:
// provide default implementations
virtual bool impl_hasHeight() const { return false; }
virtual int impl_getHeight() const { throw std::logic_error("getHeight not implemented for this object"); }
};
struct DepthProperty
{
bool hasDepth() const { return impl_hasDepth(); }
int getDepth() const { return impl_getDepth(); }
private:
virtual bool impl_hasDepth() const { return false; }
virtual int impl_getDepth() const { throw std::logic_error("getDepth not implemented for this object"); }
};
class Alma : public HeightProperty, public DepthProperty
{
public:
Alma() = default;
virtual ~Alma() = default;
// note: nonvirtual interface defers to private virtual implementation
// this is industry best practice
int getWidth() const { return impl_getWidth(); }
const std::string& type() const {
return impl_getType();
}
private:
virtual int impl_getWidth() const = 0;
virtual const std::string& impl_getType() const = 0;
};
class Birs: public Alma
{
private:
// implement the mandatory interface
int impl_getWidth() const override { return 1; }
const std::string& impl_getType() const override {
static const std::string type("Birs");
return type;
}
// implement the HeightProperty optional interface
bool impl_hasHeight() const override { return true; }
int impl_getHeight() const override { return 2; }
};
class Citrom: public Alma
{
private:
// implement the mandatory interface
int impl_getWidth() const override { return 3; }
const std::string& impl_getType() const override {
static const std::string type("Citrom");
return type;
}
// implement the DepthProperty optional interface
bool impl_hasDepth() const override { return true; }
int impl_getDepth() const override { return 4; }
};
/*...*/
/*Using them*/
// generate either a Birs or a Citrom, but return the Alma interface
std::unique_ptr<Alma> make_alma(bool borc)
{
if (borc) {
return std::make_unique<Birs>();
}
else {
return std::make_unique<Citrom>();
}
}
void Useobjects()
{
for (bool b : { true, false })
{
std::unique_ptr<Alma> pa = make_alma(b);
std::cout << "this object's typeid name is " << pa->type() << std::endl;
std::cout << "it's width is : " << pa->getWidth() << std::endl;
if(pa->hasHeight()) {
std::cout << "it's height is: " << pa->getHeight() << std::endl;
}
if(pa->hasDepth()) {
std::cout << "it's depth is: " << pa->getDepth() << std::endl;
}
}
}
int main()
{
Useobjects();
return 0;
}
expected output:
this object's typeid name is Birs
it's width is : 1
it's height is: 2
this object's typeid name is Citrom
it's width is : 3
it's depth is: 4
I have an Item class that represents an item which can be drawn on the screen. Let's say that the item can be a piece of text, an image or a solid color rect.
I also have a class that contains a collection of such items.
I can imagine two different approaches to implement these classes. I have a Drawer class to draw these items with an interface like this:
Draw(Item& item);
The first approach:
class Item
{
Point Position;
}
class Text : public Item
{
string Text;
}
class Image : public Item
{
string FilePath;
}
class Rect : public Item
{
Color FillColor;
}
class ItemCollection
{
vector<Item*> Items;
}
The first approach uses inheritance to differentiate between the different types of items. The downside to this solution is that I have to use some kind of handle (a simple pointer in the above example) when storing the items in a vector to create a heterogeneous collection, and that the Item& reference has to be casted to its concrete type in the Draw function.
The second approach:
class Item
{
ItemType Type;
Point Position;
string Text;
string FilePath;
Color FillColor;
};
enum class ItemType { Text, Image, Rect };
class ItemCollection
{
vector<Item> Items;
}
In this solution a single class contains all the data members of the different items. The benefit is that now the vector in the collection class can contain real Item values, and that no casting will be needed in the Draw function.
However, the drawback is that the memory usage of the Item class is not optimal, because every kind of item instance will contain a number of fields that it does not really need. Also, if later additional item types get added, then the Item class will get cluttered with all their new fields.
(I know that a third possible approach would be to put an abstract virtual Draw method in the base Item class, but I can not do that because in my situation these classes can only contain data, and no logic.)
Which is usually the preferred solution in a situation like this?
Visitor Pattern
I know that a third possible approach would be to put an abstract virtual Draw method in the base Item class, but I can not do that because in my situation these classes can only contain data, and no logic.
Try Visitor Pattern - all your logic will be in visitor classes not objects. Each object would have only one virtual method - accept(Visitor&).
LIVE DEMO
#include <iostream>
#include <ostream>
#include <utility>
#include <memory>
#include <vector>
#include <string>
using namespace std;
using Color = int;
struct Text;
struct Image;
struct Rect;
struct Visitor
{
virtual void operator()(const Text &x) const=0;
virtual void operator()(const Image &x) const=0;
virtual void operator()(const Rect &x) const=0;
};
struct IVisitable
{
virtual void accept(Visitor&)=0;
virtual ~IVisitable(){}
};
template<typename Derived>
struct Visitable : IVisitable
{
void accept(Visitor &v) override
{
v(*static_cast<Derived*>(this));
}
};
struct Text: Visitable<Text>
{
string Text = "text";
};
struct Image: Visitable<Image>
{
string FilePath = "path";
};
struct Rect: Visitable<Rect>
{
Color FillColor = 11;
};
struct Draw: Visitor
{
void operator()(const Text &x) const override
{
cout << "Text: " << x.Text << endl;
}
void operator()(const Image &x) const override
{
cout << "image: " << x.FilePath << endl;
}
void operator()(const Rect &x) const override
{
cout << "Rect: " << x.FillColor << endl;
}
};
int main()
{
vector<unique_ptr<IVisitable>> items;
items.emplace_back(new Text);
items.emplace_back(new Image);
items.emplace_back(new Rect);
Draw v;
for(auto &&x: items)
x->accept(v);
}
Output is:
Text: text
image: path
Rect: 11
Boost.Variant
However, the drawback is that the memory usage of the Item class is not optimal, because every kind of item instance will contain a number of fields that it does not really need. Also, if later additional item types get added, then the Item class will get cluttered with all their new fields.
Consider Boost.Variant as optimization of that technique. Your variant will have size of max item, not accumulation of all fields:
std::vector<boost::variant<Text, Image, Rect>> items;
LIVE DEMO
#include <boost/range/algorithm.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <ostream>
#include <utility>
#include <vector>
#include <string>
using namespace boost;
using namespace std;
using Color = int;
struct Text
{
string Text;
};
struct Image
{
string FilePath;
};
struct Rect
{
Color FillColor;
};
struct Draw : static_visitor<void>
{
void operator()(const Text &x) const
{
cout << "Text: " << x.Text << endl;
}
void operator()(const Image &x) const
{
cout << "image: " << x.FilePath << endl;
}
void operator()(const Rect &x) const
{
cout << "Rect: " << x.FillColor << endl;
}
};
int main()
{
vector<variant<Text, Image, Rect>> items =
{
Text{"text"},
Image{"path"},
Rect{55}
};
Draw v;
for_each(items,apply_visitor(v));
}
Output is:
Text: text
image: path
Rect: 55
(I know that a third possible approach would be to put an abstract virtual Draw method in the base Item class, but I can not do that because in my situation these classes can only contain data, and no logic.)
This is the correct approach if you are looking for polymorphism, as the question title states. Consider revising your program if it is unacceptable to you.
The other two approaches you mentioned both rely on having an ItemType variable, which is exactly what polymorphism is meant to avoid.
Also, if later additional item types get added, then the Item class will get cluttered with all their new fields.
This is also solved by polymorphism, as the Drawer implementation does not need to know about new object types.
If you are intent on keeping it simple, the first example is okay, but you need a type variable so you can tell which kind of object you're dealing with.
I have encounter a problem in my project on enums.
In EventDef.h,
enum EventDef {
EVT1 = 0,
EVT2,
EVT3,
EVT_NUM,
}
In this way, I can extend the EventDef system in another header UIEventDef.h by
#include "EventDef.h"
enum UIEventDef {
UIEVT1 = EVT_NUM,
UIEVT2,
UIEVT3,
}
But, there is a limitation that i can not do this in NetEvent.h the same way.
#include "EventDef.h"
enum NetEventDef {
NETEVT1 = EVT_NUM,
NETEVT2, //wrong: this will have the same value as UIEVT2
NETEVT3,
}
Is there a better compile time solution in C++ such as templates that can help ?
The idea of extensible enums is not inherently "bad design". In other languages there is a history of them, even if c++ does not support them directly. There are different kinds of extensibility.
Things that extensible enums would be useful for
error codes
message types
device identification (OIDs are a hierarchical enum like system)
Examples of enum extensibility
Objective Modula Two has enums that are extensible with a class like inheritance.
The Extensible Enum Pattern in Java, which can be implemented in c++.
Java enums are extensible in that extra data and methods can be a part of an enum.
In c++, the typeid operator is essentially a compiler generated enum with attached values.
The kind of extensibility you showed in your sample code does not have an elegant implementation in unaided c++. In fact, as you pointed out, it easily leads to problems.
Think about how you are wanting to use an extensible enum. Perhaps a set/map of immutable singleton objects will meet your needs.
Another way to have extensible enums in c++ is to use a code generator. Every compilation unit that wants to add to an extensible enum, records the ids in its own, separate, .enum file. At build time, before compilation, a script (ie perl, bash, ...) looks for all .enum files, reads them, assigns numeric values to each id, and writes out a header file, which is included like any other.
Why do you want your event enums to be declared like that? What do you gain by having them 'linked' if you will, they way you describe?
I would make them completely independent enums. Secondly, I recommend you not use the old style enums anymore. c++11 is here and available in gcc. You should use enum classes:
enum class EventDef : unsigned { Evt1 = 0, Evt2, Evt3, ... LastEvt }
enum class NetEvtDef : unsigned { NetEvt1 = 0, NetEvt2, NetEvt3, ... NetLastEvt }
If you are switching you can do this:
void doSwitch(EventDef evt_def)
{
switch(evt_def)
{
case EventDef::Evt1
{
// Do something;
break;
}
default:
// Do something;
};
}
void doSwitch(NetEvtDef net_def)
{
switch(net_def)
{
case NetEvtDef::NetEvt1
{
// Do something;
break;
}
default:
// Do something;
};
}
By creating an overloaded function for doSwitch you segregate all your enum types. Having them in separate categories is a benefit not a problem. It provides you the flexibility to deal with each event enum type differently.
Chaining them together as you describe needlessly complicates the problem.
I hope that helps.
I find the following a useful compromise between complexity, features, and type safety. It uses global variables of a custom class that has a default constructor to make initialisation easy. The example below is an extendable set of error codes. You might want to enclose within a name space also (but I typically don't bother).
//
// ErrorCodes.h
// ExtendableEnum
//
// Created by Howard Lovatt on 10/01/2014.
//
#ifndef ErrorCodes_h
#define ErrorCodes_h
#include <string>
class ErrorCodes {
public:
static int nextValue_;
explicit ErrorCodes(std::string const name) : value_{nextValue_++}, name_{name} {}
ErrorCodes() : ErrorCodes(std::to_string(nextValue_)) {}
int value() const { return value_; }
std::string name() const { return name_; }
private:
int const value_;
std::string const name_;
ErrorCodes(const ErrorCodes &);
void operator=(const ErrorCodes &);
};
int ErrorCodes::nextValue_ = 0; // Weird syntax, does not declare a variable but rather initialises an existing one!
ErrorCodes first;
ErrorCodes second;
// ...
#endif
//
// ExtraErrorCodes.h
// ExtendableEnum
//
// Created by Howard Lovatt on 10/01/2014.
//
#ifndef ExtraErrorCodes_h
#define ExtraErrorCodes_h
#include "ErrorCodes.h"
ErrorCodes extra{"Extra"};
#endif
//
// ExtraExtraExtraCodes.h
// ExtendableEnum
//
// Created by Howard Lovatt on 10/01/2014.
//
#ifndef ExtendableEnum_ExtraExtraCodes_h
#define ExtendableEnum_ExtraExtraCodes_h
#include "ErrorCodes.h"
ErrorCodes extraExtra{"ExtraExtra"};
#endif
//
// main.cpp
// ExtendableEnum
//
// Created by Howard Lovatt on 10/01/2014.
//
#include <iostream>
#include "ErrorCodes.h"
#include "ExtraErrorCodes.h"
#include "ExtraExtraErrorCodes.h"
// Need even more error codes
ErrorCodes const localExtra;
int main(int const notUsed, const char *const notUsed2[]) {
std::cout << first.name() << " = " << first.value() << std::endl;
std::cout << second.name() << " = " << second.value() << std::endl;
std::cout << extra.name() << " = " << extra.value() << std::endl;
std::cout << extraExtra.name() << " = " << extraExtra.value() << std::endl;
std::cout << localExtra.name() << " = " << localExtra.value() << std::endl;
return 0;
}
The output is:
0 = 0
1 = 1
Extra = 2
ExtraExtra = 3
4 = 4
If you have multiple compilation units then you need to use a variation on the singleton pattern:
class ECs {
public:
static ErrorCode & first() {
static ErrorCode instance;
return instance;
}
static ErrorCode & second() {
static ErrorCode instance;
return instance;
}
private:
ECs(ECs const&);
void operator=(ECs const&);
};
We can construct an extensible “enum” in C++ as follows:
struct Last {};
struct D
{
using Next = Last;
static const char* name = “D”;
};
struct C
{
using Next = D;
static const char* name = “C”;
};
struct B
{
using Next = C;
static const char* name = “B”;
};
using First = B;
We can iterate thru the above using these constructs:
void Process(const B&)
{
// do something specific for B
cout << “Call me Ishmael” << endl;
}
template <class T>
void Process(const T&)
{
// do something generic
cout << “Call me “ << T::name << endl;
}
template <class T>
struct IterateThru
{
static void iterate()
{
Process(T());
IterateThru<T::Next>::iterate();
}
};
template <>
struct IterateThru<Last>
{
static void iterate()
{
// end iteration
}
};
To iterate through the “enumeration”:
IterateThru<First>::iterate();
To extend the “enumeration”:
struct A
{
using Next = B;
static const char* name = “A”;
}:
using First = A: