I’m trying to write a container that handles a vector of ObjArray’s which is a templated class. ObjArray inherits from a base class and the container holds a list of base pointers which I cast to the right type when manipulating. How do I make this as failsafe as possible for the caller?
A code snippet below shows what I’m trying to do with comments about what could go wrong.
#include <iostream>
#include <vector>
#include <memory>
struct CustomA{
int a;
};
struct CustomB{
float b;
};
class IObjArray{
public:
virtual void RemoveObj(int obj_id) = 0;
virtual ~IObjArray() = default;
};
template<typename T>
class ObjArray : public IObjArray{
public:
void RemoveObj(int obj_id) override {
std::cout << "Removing object of id" << obj_id << "\n";
// Mechanism for removing object from vector below...
}
void AddT(T obj){
vec_of_T_.push_back(obj);
}
private:
std::vector<T> vec_of_T_;
};
class ObjManager{
public:
template<typename obj>
size_t RegisterObj(){
list_.emplace_back(std::make_unique<ObjArray<obj>>());
return list_.size() - 1;
}
template<typename T>
void AddObj(size_t obj_array_id, T obj){
static_cast<ObjArray<T>*>(list_[obj_array_id].get())->AddT(obj);
}
private:
std::vector<std::unique_ptr<IObjArray>> list_;
};
int main()
{
ObjManager om;
auto id_a = om.RegisterObj<CustomA>();
auto id_b = om.RegisterObj<CustomB>();
CustomA a{0};
om.AddObj(id_a, a);
om.AddObj(id_b, a); // This compiles but is bad. Anyway to stop this happening? Registering a CustomA type in a CustomB ObjArray.
// om.AddObj<CustomB>(id_b, a); // This doesn't compile because of type mismatch
return 0;
I could use some form of typeid(T) to add to a map when objects are registered and then whenever AddObj is called it could typeid(T) as a key into the map and pull the correct ObjArray. I’d rather use an ID based system thought to index into the array and to keep this as fast as possible. I also can’t add the methods AddObj to the IObjArray interface because they’re templated.
Is it possible to fix these issues or is it just important to make the caller aware?
Thanks!
If id_a
and id_b
can be of different type you can wrap the size_t
index into a class template that is tagged with the type of the element:
template <typename T>
struct Index {
size_t value = 0;
using type = T;
};
class ObjManager{
public:
template<typename obj>
Index<obj> RegisterObj(){
list_.emplace_back(std::make_unique<ObjArray<obj>>());
return {list_.size() - 1};
// ^ wrap the index
}
template<typename T>
void AddObj(Index<T> obj_array_id, T obj){
static_cast<ObjArray<T>*>(list_[obj_array_id.value].get())->AddT(obj);
// ^ unwrap it
}
private:
std::vector<std::unique_ptr<IObjArray>> list_;
};
Then here:
om.AddObj(id_a, a);
om.AddObj(id_b, a); // error, as it should
first line compiles while the second does not, because the Index
type of id_b
does not match that of b
.
is it a requirement that
id_a
andid_b
are of same type?Not necessarily. As long as
id_a
andid_b
can be used still to index into the vector some how when AddObj is called.Why do you need to cast anything? Your vector is a
std::vector<std::unique_ptr<IObjArray>
and all access should be managed through virtual functions on IObjArray. And I would just add a virtual getObjID() function to that baseclass or something.@PepijnKramer i am not sure if I understand OPs intentions, but note that
CustomA
andCustomB
do not share a common base. The inheritance is only to erase types of the arrays. Though in principle I agree, it has a bit of a flavour of inventing something complicated to avoid virtual functions.@PepijnKramer I mention why at the end of one of my paragraphs. I can’t manage access through the interface because then IObjArray will need to be templated as those access functions take the templated type. AddObj(T obj) for example. If IObjArray is templated then I can’t have a single array of IObjArray pointers.