In Java you can create a Map that maps String to Generic Object types that can be explicitly casted to other classes. Is there any nice way of imitating this functionality in C++?
2 Answers
Being a quite strongly typed language, C++ does not have a "Generic Object Type". It surely has associative containers: std::map (a flavour of binary tree) and std::unordered_map (a flavour of hash table). Which is better depends on the use case, and often can't be decided without profiling.
The closest thing to a generic object I can think of is a common ancestor for all the objects that might be put in this map. Here the idea is to create a class hierarchy with dynamic polymorphism, and to store the objects in the map as pointers cast into that common ancestor.
The ideal design would make casting of these objects back to their derived class unnecessary. If instead such cast is required, one will have to use a dynamic_cast (and possibly check that it succeeded).
It is mandatory to store pointers to the objects in the map, as opposed to the objects themselves. Otherwise, of the objects one tries to insert into the map, only the common ancestor part would be stored, and the polymorphism would be lost. It also needs to be decided whether the map owns the objects or not (no garbage collection here). If not, simple pointers may work. If the map owns the objects, I would recommend storing them wrapped in "unique pointers" (std::unique_ptr).
Wrapping up:
#include <unordered_map>
#include <string>
#include <memory> // std::unique_ptr<>, std::make_unique()
#include <iostream>
class NotSoGenericClass {
public:
virtual ~NotSoGenericClass() = default;
virtual std::string name() const
{ return "NotTooGenericClass()"; }
};
class EvenLessGenericClass: public NotSoGenericClass {
int fValue = 0;
public:
EvenLessGenericClass(int value): fValue(value) {}
virtual std::string name() const override
{ return "EvenLessGenericClass(" + std::to_string(fValue) + ")"; }
int value() const { return fValue; }
};
int main() {
//
// map holding (and owning) "not so generic objects"
//
std::unordered_map<std::string, std::unique_ptr<NotSoGenericClass>> allObjects;
//
// populate it
//
allObjects["any"] = std::make_unique<NotSoGenericClass>();
allObjects["six"] = std::make_unique<EvenLessGenericClass>(6);
allObjects["one"] = std::make_unique<EvenLessGenericClass>(1);
std::cout << "Object 'six' says: " << allObjects["six"]->name() << std::endl;
std::cout << "Now dumping all " << allObjects.size() << " objects:";
for (auto const& keyAndObject: allObjects) {
auto const& key = keyAndObject.first;
auto const* object = keyAndObject.second.get();
//
// base class interface is always available:
//
std::cout << "\n[" << key << "] " << object->name();
//
// object-specific one requires a cast:
//
auto const* lessGen = dynamic_cast<EvenLessGenericClass const*>(object);
if (lessGen) std::cout << " (value is " << lessGen->value() << ")";
} // for
std::cout << std::endl;
return 0;
} // main()
On my platform, this code (using C++14) emits:
[one] EvenLessGenericClass(1) (value is 1)
[six] EvenLessGenericClass(6) (value is 6)
[any] NotTooGenericClass()
(also illustrating the meaning of "unordered" in the map name).
This example was compiled with g++ -Wall -pedantic -std=c++14 -o test.exe test.cpp (GCC 6.4.0).
std::any. Or perhaps for a plain oldvoid*. Difficult to tell without knowing how you plan to use this thing.Map<String, Object>, because the C++ typesystem itself is too different in fundamental ways. For example,Map<String,Object>, being a java generic, is really fundamentallyMap<Object,Object>with syntactic sugar; and is a base class.std::map<std::string, Foo>in C++ is a first level object, which genuinely has std::string keys. Also, there's noObjectequivalent in C++, though there are weak types/wrappers.Map<String,Object>in Java, there's likely at least one particular way to scratch that itch in C++; generally though there's at least 5 different approaches to "mimic" this in C++ that I can think of off the top of my head (and they're all different).