I have a class, Table, which contains a member height. The value of height should be either an int or point to an object which has additional data.
The purpose here is that the user can either input a straight value, or choose a predefined value (identified by its ID) which can be shared by multiple Table objects.
This is a way I think I could achieve this functionality.
1) Create a base class Height.
2) Have two subclasses StraightHeight and PredefinedHeight
StraightHeight would simply contain an integer with the height (with getter and setter), and PredefinedHeight would contain the additional data:
class PredefinedHeight : public Height
{
public:
void setHeight(int height);
void setId(int id);
void setName(std::string name);
int getHeight();
int getId();
std::string getName();
private:
int height;
int id;
std::string name;
};
Inside Table, the height member would be of type Height.
I think this would work in practice, however I have another issue. When it comes to displaying this data, if height is a straight value (of type StraightHeight), it should just display the int height. If it is a PredefinedHeight, it should display the id and name as well. I could have a virtual method std::string getData() in both classes which returns the necessary data but it feels messy relying on the classes to format the string, etc.
Alternatively I could use a dynamic cast, but worry that it may be bad practise in this case.
Or for simplicity sake, not bother with inheritance and just have one class for Height. If it's a straight value, just leave the additional members empty.
Does anyone have a better suggestion?
Note: This is an updated, rewritten answer. The prior version is available in the edit history.
Let's look at it from data modeling perspective: Table has a property height. That property can either be owned by the Table, or can come from another source. The simplest way one could think of would be to use a pointer to a const object. The pointer should be to a const, since the height may be a preset, and those shouldn't be changeable via the Table object.
class Table {
int m_myHeight;
const int *m_height = &m_myHeight;
public:
int height() const { return *m_height; }
void setHeight(int newHeight) {
m_height = &m_myHeight;
m_myHeight = newHeight;
}
void setPresetHeight(const int &preset)
{
m_height = &preset;
/* this line is optional */ m_myHeight = *m_height;
}
};
class PresetHeights {
std::vector<int> m_data;
public:
const int &getPreset(int index);
};
This will work just fine, but you may wish to have some additional properties assigned to the preset - properties that the "embedded" height of the Table object doesn't have. For example, the preset can have a name, etc.
This can be done by holding a reference to either "just" a height, or to a height preset. Since the identifier of a height preset is used to index the identifier in a presets table, it probably makes sense to make the id value private, and only accessible via the presets table. This gives the power over ids to the presets table, and gives some freedom in how the table is implemented.
The example that follows is written in C++17, and can be tried out on godbolt.
#include <string>
#include <variant>
struct Height {
int value;
Height(int value) : value(value) {}
operator int() const { return value; }
};
struct HeightPreset : Height {
using Id = int;
private:
Id id; // the unique identifier of this preset
friend class HeightPresets;
friend int main(); // test harness
public:
std::string name; // name of this preset
template <typename Name>
HeightPreset(Id id, int value, Name &&name) :
Height(value), id(id), name(std::forward<Name>(name)) {}
};
The Table uses std::variant to hold either no height value (std::monostate), or a Height, or a HeightPreset:
#include <functional>
class Table {
using preset_t = std::reference_wrapper<const HeightPreset>;
std::variant<std::monostate, Height, preset_t> m_height;
public:
std::optional<Height> height() const {
if (auto *customHeight = std::get_if<Height>(&m_height))
return *customHeight;
else if (auto *presetHeight = std::get_if<preset_t>(&m_height))
return std::get<preset_t>(m_height).get();
else
return {};
}
void setHeight(Height newHeight)
{ m_height = newHeight; }
void setHeightPreset(const HeightPreset &preset)
{ m_height = std::cref(preset); }
bool hasPresetHeight() const { return m_height.index() == 2; }
const HeightPreset &presetHeight() const
{ return std::get<preset_t>(m_height).get(); }
};
Presets Iterable By Value of Type HeightPreset
The presets are a map from HeightPreset::Id to HeightPreset. But first, we need an iterator adapter to let us iterate the preset values - hiding the implementation detail that we use a map whose iterated values are std::pair, not HeightPreset.
#include <map>
template <class K, class V, class C, class A>
class map_cvalue_iterator
{
typename std::map<K, V, C, A>::const_iterator it;
public:
map_cvalue_iterator(typename std::map<K,V>::const_iterator it) : it(it) {}
map_cvalue_iterator(const map_cvalue_iterator &o) : it(o.it) {}
auto &operator=(const map_cvalue_iterator &o) { it = o.it; return *this; }
auto operator++(int) { auto val = *this; ++it; return val; }
auto &operator++() { ++it; return *this; }
auto operator--(int) { auto val = *this; --it; return val; }
auto &operator--() { --it; return *this; }
const V& operator*() const { return it->second; }
const V* operator->() const { return it->second; }
bool operator==(map_cvalue_iterator o) const { return it == o.it; }
bool operator!=(map_cvalue_iterator o) const { return it != o.it; }
};
template <class M>
using map_cvalue_iterator_type
= map_cvalue_iterator<typename M::key_type, typename M::mapped_type,
typename M::key_compare, typename M::allocator_type>;
The presets are a thin wrapper around std::map:
class HeightPresets {
public:
using Id = HeightPreset::Id;
HeightPresets(std::initializer_list<HeightPreset> presets)
{
for (auto &preset : presets)
m_presets.insert({preset.id, preset});
}
auto &get(Id id) const { return m_presets.at(id); }
Id getIdFor(const HeightPreset &preset) const
{ return preset.id; }
auto begin() const { return map_cvalue_iterator_type<map_t>(m_presets.cbegin()); }
auto end() const { return map_cvalue_iterator_type<map_t>(m_presets.cend()); }
private:
using map_t = std::map<Id, HeightPreset>;
map_t m_presets;
};
The simple test harness that demonstrates the use of those types:
#include <cassert>
int main() {
const HeightPresets presets{
{1, 5, "A Fiver"},
{2, 10, "A Tenner"}
};
Table aTable;
assert(!aTable.height());
// The table has no height by default
aTable.setHeight(10);
assert(!aTable.hasPresetHeight());
// The height was not a preset
assert(aTable.height() == 10);
// The height was retained
for (auto &preset : presets)
{
aTable.setHeightPreset(preset);
assert(aTable.hasPresetHeight());
// The height was preset
assert(aTable.height() == preset);
// The height has the expected preset's value
assert(presets.getIdFor(aTable.presetHeight()) == preset.id);
// The height has the expected preset's identifier
assert(aTable.presetHeight().name == preset.name);
}
}
Presets Iterable by std::pair<HeightPreset::Id, HeightPreset>
If you wish not to use the iterator adapter, it can be removed. See below, and also try it out on godbolt.
#include <map>
class HeightPresets {
public:
using Id = HeightPreset::Id;
HeightPresets(std::initializer_list<HeightPreset> presets)
{
for (auto &preset : presets)
m_presets.insert({preset.id, preset});
}
auto &get(Id id) const { return m_presets.at(id); }
Id getIdFor(const HeightPreset &preset) const
{ return preset.id; }
auto begin() const { return m_presets.cbegin(); }
auto end() const { return m_presets.cend(); }
private:
using map_t = std::map<Id, HeightPreset>;
map_t m_presets;
};
And the test harness:
#include <cassert>
int main() {
const HeightPresets presets{
{1, 5, "A Fiver"},
{2, 10, "A Tenner"}
};
Table aTable;
assert(!aTable.height());
// The table has no height by default
aTable.setHeight(10);
assert(!aTable.hasPresetHeight());
// The height was not a preset
assert(aTable.height() == 10);
// The height was retained
for (auto &presetPair : presets)
{
auto &preset = presetPair.second;
aTable.setHeightPreset(preset);
assert(aTable.hasPresetHeight());
// The height was preset
assert(aTable.height() == preset);
// The height has the expected preset's value
assert(presets.getIdFor(aTable.presetHeight()) == preset.id);
// The height has the expected preset's identifier
assert(aTable.presetHeight().name == preset.name);
}
}
The value of height should be either an int or point to a struct which has additional data
This is called a sum type a.k.a. a tagged union.
C++ has a standard template for that: std::variant from the standard <variant> header. You probably want to use some smart pointer inside.
If you cannot use that header, re-implement it using some union inside your class (with another member discriminating that union, inspired by this), but don't forget to follow the rule of five.
I recommend reading more about C++. First, Programming -- Principles and Practice Using C++ and later the C++11 standard n3337.
Related
Assume C++17 (some of this is deprecated in C++20)
I have written a class EventDB that stores a fixed set of shared_ptr.
class EventDB
{
public:
EventDB() = delete;
EventDB(const std::vector<std::shared_ptr<const EventInfo>>& init_events):
PointEvents(init_events.begin(),init_events.end())
{}
std::shared_ptr<const EventInfo> Swap(std::shared_ptr<const EventInfo> event)
{
auto old_it = PointEvents.find(event);
if(old_it == PointEvents.end())
return nullptr;
//cast away the constness of the iterator element
//is this OK, because we know we're not changing its hash/equality?
auto old_evt_addr = &const_cast<std::shared_ptr<const EventInfo>&>(*old_it);
return std::atomic_exchange(old_evt_addr,event);
}
private:
std::unordered_set<std::shared_ptr<const EventInfo>,EventPointHash,EventPointEq> PointEvents;
};
It provides a way to swap out the elements of the set using std::atomic_exchange.
'swapping out' an element of a set may seem pointless, but I provide custom hasher and equality for the set, so the swapped elements actually hold different data even though they're equivalent from the perspective of the set. The correctness of all this is the subject of a secondary question, because I could just replace it with a map if I needed to.
My main question is about thread safety - is EventDB thread safe, and if not, why not?
The secondary question alluded to above is how bad it is to cast away the constness of the set iterator so I can (atomically) modify the element. Am I breaking the rules of the language and relying on implementation specific behaviour? Or is this technically allowed?
For bonus kudos, what would I replace std::atomic_exchange with in C++20. I know there are proper atomic smart pointer, but could I convert between normal shared_ptr in this example?
Here's some self-contained code that compiles and works with g++ 9.3.0 GLIBCXX_3.4.28
#include <vector>
#include <string>
#include <iostream>
#include <thread>
#include <memory>
#include <limits>
#include <unordered_set>
enum class EventType : uint8_t
{
RED = 0,
BLUE = 1
};
class EventInfo
{
public:
EventInfo() = delete;
EventInfo(const EventType t, const size_t i, const std::string& p):
Type(t),Index(i),Payload(p)
{}
size_t GetIndex() const
{
return Index;
}
EventType GetEventType() const
{
return Type;
}
const std::string& GetPayload() const
{
return Payload;
}
private:
EventType Type;
size_t Index;
std::string Payload;
};
struct EventPointHash
{
size_t operator()(const std::shared_ptr<const EventInfo>& evt) const
{
if(!evt)
return std::numeric_limits<size_t>::max();
return (evt->GetIndex() << (sizeof(EventType)<<3)) + static_cast<size_t>(evt->GetEventType());
}
};
struct EventPointEq
{
bool operator()(const std::shared_ptr<const EventInfo>& lhs,
const std::shared_ptr<const EventInfo>& rhs) const
{
if(!lhs && !rhs) return true;
if(!lhs || !rhs) return false;
return (lhs->GetIndex() == rhs->GetIndex() && lhs->GetEventType() == rhs->GetEventType());
}
};
class EventDB
{
public:
EventDB() = delete;
EventDB(const std::vector<std::shared_ptr<const EventInfo>>& init_events):
PointEvents(init_events.begin(),init_events.end())
{}
std::shared_ptr<const EventInfo> Swap(std::shared_ptr<const EventInfo> event)
{
auto old_it = PointEvents.find(event);
if(old_it == PointEvents.end())
return nullptr;
//cast away the constness of the iterator element
//is this OK, because we know we're not changing its hash/equality?
auto old_evt_addr = &const_cast<std::shared_ptr<const EventInfo>&>(*old_it);
return std::atomic_exchange(old_evt_addr,event);
}
private:
std::unordered_set<std::shared_ptr<const EventInfo>,EventPointHash,EventPointEq> PointEvents;
};
int main()
{
//create a database to hold 100 events
std::vector<std::shared_ptr<const EventInfo>> init_events;
for(int i=0;i<100;i++)
{
init_events.emplace_back(std::make_shared<const EventInfo>(EventType::RED,i,"-1"));
}
EventDB DB(init_events);
//Access the element concurrently
std::vector<std::thread> threads;
for(int i = 0;i<5;i++)
{
threads.emplace_back([&]()
{
for(int j = 0;j<1000000;j++)
{
//replace a random element
auto event = std::make_shared<const EventInfo>(EventType::RED,rand()%100,std::to_string(j));
auto old_evt = DB.Swap(event);
//access the data - randomly print
if(old_evt && std::stoi(old_evt->GetPayload())%2000 == 0 && old_evt->GetIndex() == 66)
std::cout<<"Replaced "<<old_evt->GetPayload()<<" with "<<event->GetPayload()<<std::endl;
}
});
}
init_events.clear();
for(auto& t : threads)
t.join();
return 0;
}
Typical output:
Replaced 20000 with 20033
Replaced 134000 with 134002
Replaced 144000 with 143694
Replaced 144000 with 144435
Replaced 172000 with 174980
Replaced 252000 with 255578
Replaced 258000 with 252434
Replaced 368000 with 367261
Replaced 498000 with 497470
Replaced 584000 with 583205
Replaced 628000 with 619809
Replaced 722000 with 722603
Replaced 730000 with 722302
Replaced 780000 with 768508
Replaced 784000 with 784036
Replaced 816000 with 821799
Replaced 842000 with 844719
Replaced 970000 with 950851
Edit:
Igor's answer pointed me to the data race. I was then able to easily modify the code to prove it in practice.
Adding a destructor that messed up the hash if a destroyed element was used, and then printing a message when find failed:
~EventInfo()
{
//these aren't used in the example
// - so they will mess up find when the race is lost
Index = 200;
Type = EventType::BLUE;
}
auto old_evt = DB.Swap(event);
if(!old_evt)
std::cout<<"BOOM"<<std::endl;
And sure enough:
BOOM
BOOM
BOOM
BOOM
Fixed code (unless someone finds something else!)##
This is my attemp to implement the fix suggested in Igor's answer
#include <vector>
#include <string>
#include <iostream>
#include <thread>
#include <memory>
#include <limits>
#include <unordered_map>
enum class EventType : uint8_t
{
RED = 0,
BLUE = 1
};
class EventInfo
{
public:
EventInfo() = delete;
EventInfo(const EventType t, const size_t i, const std::string& p):
Type(t),Index(i),Payload(p)
{}
size_t GetIndex() const
{
return Index;
}
EventType GetEventType() const
{
return Type;
}
const std::string& GetPayload() const
{
return Payload;
}
private:
EventType Type;
size_t Index;
std::string Payload;
};
struct EventPointHash
{
size_t operator()(const std::pair<EventType,size_t>& point) const
{
return (point.second << (sizeof(EventType)<<3)) + static_cast<size_t>(point.first);
}
};
class EventDB
{
public:
EventDB() = delete;
EventDB(const std::vector<std::shared_ptr<const EventInfo>>& init_events)
{
for(const auto& event : init_events)
PointEvents[{event->GetEventType(),event->GetIndex()}] = event;
}
std::shared_ptr<const EventInfo> Swap(const std::shared_ptr<const EventInfo> event)
{
auto old_it = PointEvents.find({event->GetEventType(),event->GetIndex()});
if(old_it == PointEvents.end())
return nullptr;
auto old_evt_addr = &(old_it->second);
return std::atomic_exchange(old_evt_addr,event);
}
private:
std::unordered_map<std::pair<EventType,size_t>,std::shared_ptr<const EventInfo>,EventPointHash> PointEvents;
};
int main()
{
//create a database to hold 100 events
std::vector<std::shared_ptr<const EventInfo>> init_events;
for(int i=0;i<100;i++)
{
init_events.emplace_back(std::make_shared<const EventInfo>(EventType::RED,i,"-1"));
}
EventDB DB(init_events);
init_events.clear();
//Access the element concurrently
std::vector<std::thread> threads;
for(int i = 0;i<5;i++)
{
threads.emplace_back([&]()
{
for(int j = 0;j<1000000;j++)
{
//replace a random element
auto event = std::make_shared<const EventInfo>(EventType::RED,rand()%100,std::to_string(j));
auto old_evt = DB.Swap(event);
if(!old_evt)
{
std::cout<<"BOOM"<<std::endl;
continue;
}
//access the data - randomly print
if(std::stoi(old_evt->GetPayload())%2000 == 0 && old_evt->GetIndex() == 66)
std::cout<<"Replaced "<<old_evt->GetPayload()<<" with "<<event->GetPayload()<<std::endl;
}
});
}
for(auto& t : threads)
t.join();
return 0;
}
There's a data race. While writing the set's elements is atomic via std::atomic_exchange, reading them inside find is not. find may see a torn read if another thread swaps an element right from under it.
There's a subtler scenario: one thread has called PointEvents.find(event), and find is currently reading the contents of some EventInfo instance in the set (let's call it X), to compute its hash or compare it to event. At the same time, another thread performs Swap on that same element and returns the shared pointer holding X to the caller. The caller perhaps looks at X briefly then allows the shared pointer to be destroyed, and X together with it. find then races with X's destructor.
Consider separating fixed parts of EventInfo that contribute to the hash and equality, from the payload part that can vary. Store them in std::unordered_map, with fixed part as the key and the payload as the value. Then you can swap the payload without affecting find.
So I am pretty new to C++ and am trying to understand smart pointers and the RAII design pattern. My question is this: say I have an object that contains a map of objects. I want one object at a time to be active, that is a want a pointer that points to one of the objects from the map. What is a proper RAII way to go about it using smart pointers? Below is what I have tried so far.
//StateMachine.h
std::unique_ptr<GameObject> p1Paddle = std::make_unique<GameObject>(Paddle());
std::unique_ptr<GameObject> p2Paddle = std::make_unique<GameObject>(Paddle());
std::unique_ptr<GameObject> ball = std::make_unique<GameObject>(Ball());
//StateMachine.cpp
StateMachine::StateMachine()
{
gameObjects["p1Paddle"] = std::pair <bool, std::unique_ptr<GameObject>>(false, std::move(p1Paddle));
gameObjects["p2Paddle"] = std::pair <bool, std::unique_ptr<GameObject>>(false, std::move(p2Paddle));
gameObjects["ball"] = std::pair <bool, std::unique_ptr<GameObject>>(false, std::move(ball));
}
void StateMachine::ChangeState(std::string key)
{
activeObject = std::move(gameObjects[key]);
}
Instead of using map and smart-pointer and based on your requirements that you only want a single object type at a given moment of time, perhaps you want a priority queue based implementation instead...
Here's a pseudo implementation:
#include <algorithm>
#include <cstdint>
#include <queue>
#include <string>
#include <vector>
enum class ComponentType {
PADDLE,
BALL
};
class Component {
protected:
ComponentType type_;
uint32_t id_;
priority_;
Component(ComponentType type, uint32_t id, float priority)
: type_{type}, id_{id}, priority_{prioity};
public:
virtual ~Component() {}
auto type() const { return type_; }
auto id() const { return id_; }
auto priority() const { return priority_; }
void updatePriority(float newPriority) { priority_ = newPriority; }
};
class Paddle : public Component {
private:
std::string name_;
public:
Paddle(std::string_view name, float priority) :
Component(ComponetType::PADDLE, std::stoi(name.data()), priority),
name_{name}
{}
auto name() const { return name_; }
};
class Ball : public Component {
private:
std::string name_;
public:
Ball(std::string_view name, float priority) :
Component(ComponentType::BALL, std::stoi(name.data()), priority),
name_{name}
{}
auto name() const { return name_; }
};
class CmpFunc {
public:
int operator(){ const Component& a, const Component& b) {
return a.priority() > b.priority();
}
};
class Game {
private:
//std::vector<Component*> components_;
std::priority_queue<Component*, std::vector<Component*>, CmpFunc> priority_;
public:
void initialize() {
Paddle paddle1("paddle_1", 0.1f);
Paddle paddle2("paddle_2", 0.2f);
Ball ball("ball", 0.3f");
addComponent(&paddle1);
addComponent(&paddle2);
addComponent(&ball);
}
void addComponent(Component* component) {
if (component == nullptr)
throw std::runtime_exception( "invalid component pointer!" );
//components_.push_back(component);
priority_.push(component);
}
auto getComponent() {
if (!priority_.empty()) {
auto component = priority_.top();
priority_.pop();
/*components_.erase(
std::remove(components_.begin(), components_.end(), component),
components_.end()
);*/
return component;
}
return nullptr;
}
};
This is just a pseudo example code to show a priority queue... I didn't show any implementation of updating or changing the priority queue of an object, nor did I show, how to directly use it based on some state X of the game or finite-state-machine... That would be an exercise for you...
I can not say whether this code will compile and run since I typed it out right here and have not yet tested it hence the pseudo code. You can play around with it and try to get it to compile and from there expand it to fit your own needs...
I'd like to simplify the code I write in my application that handles mutiple data structure types but with a common header. Given something like this:
enum class MyType {
Foo = 100,
Bar = 200,
};
struct Hdr {
MyType type;
};
struct Foo {
Hdr hdr;
int x;
int y;
int z;
};
struct Bar {
Hdr hdr;
double value;
double ratio;
};
void process(const Foo *ptr)
{
// process Foo here
}
void process(const Bar *ptr)
{
// process Bar here
}
extern void *getData();
int main()
{
const void *pv = getData();
auto pHdr = static_cast<const Hdr *>(pv);
switch (pHdr->type) {
case MyType::Foo: process(static_cast<const Foo *>(pv)); break;
case MyType::Bar: process(static_cast<const Bar *>(pv)); break;
default: throw "Unknown";
}
return 0;
}
Ideally I'd like to replace the switch statement above with something like:
process(multi_cast<pHdr->type>(pv);
I'm perfectly okay with having to write statements like this to get it to work:
template<MyType::Foo>
const Foo *multi_cast(void *p)
{
return static_cast<const Foo *>(p);
}
template<MyType::Bar>
const Bar *multi_cast(void *p)
{
return static_cast<const Bar *>(p);
}
But I cannot write a template where the template parameter is a enum (or an int for that matter)
Have I just looked at this for so long that I cannot see an answer?
Or is there just no other way to do it?
There is just no other way to do it.
As the comments have pointed out, since the type is stored in the header at run-time, you have to have some kind of run-time lookup; no amount of templates or overload resolution can help you since all of that is at compile-time.
You can abstract the lookup as much as you want, but you can only replace the switch statement with another type of lookup, and you can only decrease performance the further you get away from a simple switch/lookup table.
For example, you could start with something like this and go nuts:
#include <iostream>
#include <cassert>
enum class Type {
FOO,
BAR,
NUM_
};
struct Header {
Header(Type t)
: type(t)
{}
Type type;
};
struct Foo {
Foo(int x, int y, int z)
: header(Type::FOO), x(x), y(y), z(z)
{}
Header header;
int x;
int y;
int z;
};
struct Bar {
Bar(double value, double ratio)
: header(Type::BAR), value(value), ratio(ratio)
{}
Header header;
double value;
double ratio;
};
static inline void process(Foo*) {
printf("processing foo...\n");
}
static inline void process(Bar*) {
printf("processing bar...\n");
}
using ProcessFunc = void(*)(void*);
static ProcessFunc typeProcessors[(size_t)Type::NUM_] = {
[](void* p) { process((Foo*)p); },
[](void* p) { process((Bar*)p); },
};
static void process(void* p) {
Type t = ((Header*)p)->type;
assert((size_t)t < (size_t)Type::NUM_ && "Invalid Type.");
typeProcessors[(size_t)t](p);
}
static void* get_foo()
{
static Foo foo(0, 0, 0);
return &foo;
}
static void* get_bar()
{
static Bar bar(0.0, 0.0);
return &bar;
}
int main() {
Foo foo(0, 0, 0);
Bar bar(0.0, 0.0);
process(&foo);
process(&bar);
process(get_foo());
process(get_bar());
return 0;
}
but then you're only getting cute and most likely slower. You might as well just put the switch in process(void*)
If you aren't serializing your data(doubtful), are mostly processing one type at a time, and want an OO solution(I wouldn't), you could return a base type that your types inherit from and add a pure virtual process function like so:
struct Type {
virtual void process() = 0;
virtual ~Type() {}
};
struct Foo : Type {
int x = 0;
int y = 0;
int z = 0;
virtual void process() override {
printf("processing foo...\n");
}
};
struct Bar : Type {
double value = 0.0;
double ratio = 0.0;
virtual void process() override {
printf("processing bar...\n");
}
};
static Type* get_foo() {
static Foo foo;
return &foo;
}
static Type* get_bar() {
static Bar bar;
return &bar;
}
int main() {
Foo foo;
Bar bar;
foo.process();
bar.process();
get_foo()->process();
get_bar()->process();
return 0;
}
I would stick with the switch, but I would keep the values of Type::FOO and Type::BAR the default 0 and 1. If you mess with the values too much, the compiler might decide to implement the switch as a bunch of branches as opposed to a lookup table.
You have two issues:
Converting a runtime value (your "type") into a compile time determined type (with associated behavior).
"Unifying" the possible different types to a single (statically at compile time known) type.
Point 2 is what inheritance together with virtual member functions are for:
struct Thing {
virtual void doStuff() const = 0;
virtual ~Thing() {}
};
struct AThing : Thing {
void doStuff() const override { std::cout << "A"; }
};
struct BThing : Thing {
void doStuff() const override { std::cout << "B"; }
};
Point 1 is usually tackled by creating some kind of "factory" mechanism, and then dispatching based on the runtime value to one of those factories. First, the factories:
Thing * factoryA() { return new AThing(); }
Thing * factoryB() { return new BThing(); }
Thing * factory_failure() { throw 42; }
The "dispatching" (or "choosing the right factory") can be done in different ways, one of those being your switch statement (fast, but clumsy), linear search through some container/array (easy, slow) or by lookup in a map (logarithmic - or constant for hashing based maps).
I chose a (ordered) map, but instead of using std::map (or std::unordered_map) I use a (sorted!) std::array to avoid dynamic memory allocation:
// Our "map" is nothing more but an array of key value pairs.
template <
typename Key,
typename Value,
std::size_t Size>
using cmap = std::array<std::pair<Key,Value>, Size>;
// Long type names make code hard to read.
template <
typename First,
typename... Rest>
using cmap_from =
cmap<typename First::first_type,
typename First::second_type,
sizeof...(Rest) + 1u>;
// Helper function to avoid us having to specify the size
template <
typename First,
typename... Rest>
cmap_from<First, Rest...> make_cmap(First && first,
Rest && ... rest) {
return {std::forward<First>(first), std::forward<Rest>(rest)...};
}
Using std::lower_bound I perform a binary search on this sorted array (ehm "map"):
// Binary search for lower bound, check for equality
template <
typename Key,
typename Value,
std::size_t Size>
Value get_from(cmap<Key,Value,Size> const & map,
Key const & key,
Value alternative) {
assert(std::is_sorted(std::begin(map), std::end(map),
[](auto const & lhs, auto const & rhs) {
return lhs.first < rhs.first; }));
auto const lower = std::lower_bound(std::begin(map), std::end(map),
key,
[](auto const & pair, auto k) {
return pair.first < k; });
if (lower->first == key) {
return lower->second;
} else {
// could also throw or whatever other failure mode
return alternative;
}
}
So that, finally, I can use a static const map to get a factory given some runtime value "type" (or choice, as I named it):
int main() {
int const choices[] = {1, 10, 100};
static auto const map =
make_cmap(std::make_pair(1, factoryA),
std::make_pair(10, factoryB));
try {
for (int choice : choices) {
std::cout << "Processing choice " << choice << ": ";
auto const factory = get_from(map, choice, factory_failure);
Thing * thing = factory();
thing->doStuff();
std::cout << std::endl;
delete thing;
}
} catch (int const & value) {
std::cout << "Caught a " << value
<< " ... wow this is evil!" << std::endl;
}
}
(Live on ideone)
The initialization of that "map" could probably made constexpr.
Of course instead of raw pointers (Thing *) you should use managed pointers (like std::unique_ptr). Further, if you don't want to have your processing (doStuff) as member functions, then just make a single "dispatching" (virtual) member function that calls out to a given function object, passing the own instance (this). With a CRTP base class, you don't need to implement that member function for every one of your types.
You're using something that may be called static (=compile-time) polymorphism. This requires to make such switch statements in order to convert the run-time value pHrd->dtype to one of the compile-time values handles in the case clauses. Something like your
process(multi_cast<pHdr->type>(pv);
is impossible, since pHdr->type is not known at compile time.
If you want to avoid the switch, you can use ordinary dynamic polymorphism and forget about the enum Hdr, but use a abstract base class
struct Base {
virtual void process()=0;
virtual ~Base() {}
};
struct Foo : Base { /* ... */ };
struct Bar : Base { /* ... */ };
Base*ptr = getData();
ptr->process();
I have a class idx_aware that goes into a container container, which wraps around a std::vector. When the class is added to container, container sets a pointer to itself in idx_aware, as well as the index of idx_aware in its internal memory storage.
The index is not going to change until the container is destroyed or idx_aware is removed; idx_aware needs to know about its container and its index, because it has some methods that require both to work.
Now this introduces the following problem: when I get a non-const reference to an idx_aware class contained in container, I could assign to it another idx_aware class, which could have a different index. The intention would be assigning all the fields and keeping the index as it is.
#include <vector>
#include <limits>
#include <iostream>
class container;
// Stores a std::size_t field, which can be set only by subclasses.
class with_idx {
std::size_t _i;
public:
with_idx() : _i(std::numeric_limits<std::size_t>::max()) {}
operator std::size_t() const { return _i; }
protected:
void set_idx(std::size_t i) { _i = i; }
};
// Knows its index and its container
class idx_aware : public with_idx {
container const *_container;
int _some_field1;
float _some_field2;
public:
void foo() {
// Do stuff using _container and _i
}
private:
friend class container;
};
// Wraps around a std::vector
class container {
std::vector<idx_aware> _data;
public:
idx_aware &operator[](std::size_t idx) {
// Need non-const access to call foo
return _data[idx];
}
idx_aware const &operator[](std::size_t idx) const {
return _data[idx];
}
std::size_t add(idx_aware const &item) {
// Here it could potentially reuse a freed position
std::size_t free_slot = _data.size();
// Ensure _data is big enough to contain free_slot
if (_data.size() <= free_slot) {
_data.resize(free_slot + 1);
}
// Assign
_data[free_slot] = item;
_data[free_slot].set_idx(free_slot);
_data[free_slot]._container = this;
return free_slot;
}
};
int main() {
container c;
idx_aware an_item;
std::size_t i = c.add(an_item);
std::cout << c[i] << std::endl; // Prints 0
idx_aware another_item; // Created from somewhere else
// I want to set all the data in idx_aware, but the
// index should stay the same!
c[i] = another_item;
std::cout << c[i] << std::endl; // Prints numeric_limits<size_t>::max()
// Now container[i] is broken because it doesn't know anymore its index.
return 0;
}
One possible workaround would be to change with_idx in such a way that when set_idx is called, a flag is set that prevents assignment and copy operator to overwrite the _i property, like this:
class with_idx {
std::size_t _i;
bool _readonly;
public:
with_idx() : _i(std::numeric_limits<std::size_t>::max()), _readonly(false) {}
with_idx(with_idx const &other) : _i(other._i), _readonly(false) {}
with_idx &operator=(with_idx const &other) {
if (!_readonly) {
_i = other._i;
}
return *this;
}
operator std::size_t() const { return _i; }
protected:
void set_idx(std::size_t i) {
_i = i;
if (i != std::numeric_limits<std::size_t>::max()) {
// This has been set by someone with the right to do so,
// prevent overwriting
_readonly = true;
} else {
// Removed from the container, allow overwriting
_readonly = false;
}
}
};
This would have the consequence of returning, after assignment, a reference to an idx_aware class with unchanged index.
idx_aware ¬_in_container1 = /* ... */;
idx_aware ¬_in_container2 = /* ... */;
idx_aware &in_container = /* ... */;
not_in_container1 = in_container = not_in_container2;
// std::size_t(not_in_container_1) != std::size_t(not_in_container_2)
Is there a design pattern that can model this situation in a better way? My searches were not successful.
Are there other unwanted consequences of overriding the assignment operator in this way? The limitation I pointed out in the previous example does not look too "bad".
Is there an easier solution? I thought about writing some proxy object to replace the idx_aware & return type of operator[].
Experience tells that when C++ does not do what you intend, you are likely to be misusing OOP...
Robert's comment suggested me this solution.
Why would the contained object know about its container? To be able to perform actions such as foo and provide shorthand methods that otherwise would require to have access to the container.
Let's take this functionality away from the contained object; the contained object is just data payload. Instead, let's make operator[] return not the contained object, but some sort of iterator, a wrapper around the contained object, which knows the container and the index, and once dereferenced returns the actual contained object.
class was_idx_aware {
int _some_field1;
float _some_field2;
};
class container {
std::vector<idx_aware> _data;
public:
class idx_aware_wrapper {
container const *_container;
std::size_t _idx;
public:
idx_aware_wrapper(container const &c, std::size_t i)
: _container(&c)
, _idx(i)
{}
was_idx_aware const &operator*() const {
return _container->_data[_idx];
}
was_idx_aware &operator*() {
return _container->_data[_idx];
}
void foo() {
// Do stuff using _container and _idx.
}
};
idx_aware_wrapper operator[](std::size_t i) {
return idx_aware_wrapper(*this, i);
}
/* .... */
};
This allows quick access to any data in was_idx_aware, and the wrapper class can be augmented with all the methods that require interaction with the container. No need to store and keep indices up to date or override assignment operators.
How would I return a(n existing) pointer to a known type from a string of its name? Say I created some map<string, double> called map1 and some vector<string> called vec1. I'd like to write a function that will return map1 from "map1" and vec1 from "vec1" (and an additional argument specifying the respective type of each).
Is this possible?
My ultimate goal is to get a QWidget by its name in QString form, since I'm working with a large number of them, though an answer should be framework-independent, right?
You need to perform some kind of dynamic dispatch. To do so you can simply start with exactly what you proposed:
enum class types { A = 0, B = 1 };
void (*handlers[])(void*) = { &HandleA, &HandleB };
::std::unordered_map<::std::string, ::std::tuple<types, void*>> registry;
Now all that remains is to perform the lookup:
void lookup(::std::string const& name)
{
auto& t = registry.at(name);
handlers[static_cast<size_t>(::std::get<0>(t))](::std::get<1>(t));
}
Automagic argument casting for handlers
The handlers all take an argument of type void* - this can be dealt with by adding a little template magic:
template<typename T, void(*f)(T*)>
void handle(void* arg)
{
f(static_cast<T*>(arg));
}
void (*handlers[])(void*) = { &handle<A, &HandleA>, &handle<B, &HandleB> };
Now, the prototype is e.g. void HandleA(A*).
Simple adding of objects to registry
With the current code, you can add objects to your registry like so:
A a;
registry.emplace("A #1", ::std::make_tuple(types::A, &a));
While this works perfectly, we would like to do something a bit more elegant. Let us start by changing the enum class types to something which also knows about the type we whish to represent it:
template<typename T> struct types;
template<> struct types<A> { static const size_t id = 0; };
template<> struct types<B> { static const size_t id = 1; };
Of course, now we need to fix the registry type:
::std::unordered_map<::std::string, ::std::tuple<size_t, void*>> registry;
And finally we can provide a simple insert function:
template<typename T>
void insert(::std::string const& name, T* object)
{
registry.emplace(name, ::std::make_tuple(types<T>::id, static_cast<void*>(object)));
}
Final usage example
A a;
insert("A #1", &a);
lookup("A #1");
The meta-object system already handles this, so the answer will be framework-specific because you generally need a code generator to get metadata about the C++ types that's not otherwise available.
QLineEdit * ed = ...;
ed->setObjectName("myObject");
... elsewhere in the code
foreach(QWidget * w, QCoreApplication::allWidgets()) {
// Lookup by name
if (w->objectName() == "myObject") {
...
}
// Lookup by type
if (qobject_cast<QLineEdit*>(w)) {
...
}
}
If you want to speed up the lookup, and the objects have unique names:
class Widgets {
typedef QMap<QString, QPointer<QWidget>> Data;
mutable Data m_map;
public:
Widgets() {
foreach(QWidget * w, QCoreApplication::allWidgets()) {
if (w->objectName().isEmpty()) continue;
m_map.insert(w->objectName(), w);
}
}
QWidget * lookupWidget(const QString & name) const {
Data::iterator it = m_map.find(name);
if (it == m_map.end()) return nullptr;
QWidget * w = it->data();
if (!w) m_map.erase(it); // The widget doesn't exist anymore
return w;
}
template <typename T> T * lookup(const QString & name) const {
return qobject_cast<T*>(lookupWidget(name));
}
void setName(QWidget * w, const QString & name) {
Q_ASSERT(! name.isEmpty());
w->setObjectName(name);
m_map.insert(name, w);
}
};
In your code, use widgets->setName() instead of setObjectName.
If you want to look-up by both name and type, where duplicate names are OK as long as they are all of different types:
class Widgets2 {
typedef QPair<QString, QString> Key;
typedef QMap<Key, QPointer<QWidget>> Data;
mutable Data m_map;
static Key keyFor(QWidget * w) {
return qMakePair(w->objectName(),
QString::fromLatin1(w->metaObject()->className()));
public:
Widgets2() {
foreach(QWidget * w, QCoreApplication::allWidgets()) {
if (w->objectName().isEmpty()) continue;
m_map.insert(keyFor(w), w);
}
}
QWidget * lookupWidget(const QString & name, const QString & type) const {
Data::iterator it = m_map.find(qMakePair(name, type));
if (it == m_map.end()) return nullptr;
QWidget * w = it->data();
if (!w) m_map.erase(it); // The widget doesn't exist anymore
return w;
}
template <typename T> T * lookup(const QString & name) const
{
return qobject_cast<T*>(lookupWidget(name,
QString::fromLatin1(T::staticMetaObject.className())));
}
void setName(QWidget * w, const QString & name) {
Q_ASSERT(! name.isEmpty());
w->setObjectName(name);
m_map.insert(keyFor(w), w);
}
};
The lookup works as follows:
widgets2->lookup<QLineEdit>("myObject")->setText("foo");
I'm leveraging the QObject and QPointer to make the widget registry safe to widget deletions - you won't ever get a dangling pointer back.
It is also possible to track object name changes, if you so wish: QObject emits the objectNameChanged signal.
All of this is of course a horrible hack around a broken design of your code. The fact that you need this means that you're very tightly coupling business logic and the GUI. You should use some kind of model-view architecture.