I wouldn't call this collection a pool. The point of having a pool of objects is to reduce instantiation costs of multiple instances of the same type, allowing the client to "return" the resource back to the pool after using it. Your devices cannot be interchanged transparently, and the point of the collection is not related to resource usage at all.
There are two approaches that come to my mind:
You can use a
map
to quickly fetch a certainDevice
using its unique key (e.g.std::map<std::string, Device>
, but you should probably have a better idea of what to use as the key instead of a plain string), similar to:// find the right device instance string deviceInfo = "some-unique-key"; Device device = devicesMap.find(deviceInfo)->second; // send the data string json = serialize(dataObject); device.send(json);
On the other hand, you might want to delegate the actual (de)serialization to the
Device
itself, in case that the protocol might differ for some devices:// find the right device instance string deviceInfo = "some-unique-key"; Device device = devicesMap.find(deviceInfo)->second; // send the object, Device class will know how to serialize it device.send(dataObject);
Another interesting alternative is to send the messages to all
Device
instances, and let them decide if the message is relevant for them (based on whatever you think it's appropriate). This approach allows scenarios where multiple devices can dispatch the same message, if they feel like it, or simply be notified that something happened without even sending anything:// notify all devices that we have a message for (auto &device : devicesVector) { // you are only notifying the device. // each instance should decide whether or not to // pass the message forward (based on its contents), // and how to serialize it device.notify(dataObject); }
The fact that your current implementation is a Singleton is a separate issue. Singleton is a creational pattern, related to the object's lifetime, and is usually used only to avoid passing the dependency around. It simplifies ctor signatures a bit, but there might be some laziness involved also, especially considering the fact that you make unit-testing a bit harder.
Proper dependency injection patterns would require you to pass this service explicitly to all consumers (usually thorough constructor injection), which would result in less coupling and in turn allow easier unit testing. This doesn't mean you need to refactor your entire code to use a DI container, but simply pass the "thingy" which handles communication to its callers through their constructors.