My library gets information from a message bus in the form of strings. These strings are identifiers for different types of entities, which need to be processed.
Currenly methods might look like this:
void doWork(const std::string& carModelId,
const std::string& dealershipId,
const std::string& invoiceId);
float getPrice(const std::string& carModelId,
const std::string& dealershipId);
Since these different types of IDs are used all over the place, I would like to make sure the correct type of id is passed to a method, instead of using a plain string.
My current idea is to create some structs which wrap these strings
struct carModel
{
std::string id;
}
so that these methods only accept the correct type of id
void doWork(const carModel& carModelId,
const dealership& dealershipId,
const invoice& invoiceId);
On the other hand this seems to be a lot of additional boilerplate code for a common problem.
Question: what is the best practice to get type-safety for string parameters?
Best practice really depends on your software, environment, IDE, compiler and so on. You get the point.
In this specific scenario I see absolutely no problem in having
struct SomeId {
std::string id;
}
as long as you keep everything consistent over the whole application. You get the main advantage that later on you can customize these structs with additional behavior (get usage of an id, have conversions between different data types, get nice print names for debugging purposes, keep track of used Ids over the application and so on). Is nice since everything that happens is transparent to the user of your classes.
Plus, additional validation is possible with these types of structs automatically (is this id a valid one? is it already available? should be). How much you need those things and if they are actually possible depends on the logic of your application.
You should always keep it simple and straightforward and in this case what you are actually doing fit the problem description very well. Don't over complicate things more than they need to be.
HOWEVER, one word of advice: stay away in this case from explicit conversion operators and as such. You should prevent the following code from compiling.
SomeId id = someOtherIdType;
You can use tricks such as having operator()
or operator->
for syntax sugars but that is as far as I would go.
And to implement once and use everywhere you can have everything in a base class like so
struct BaseId
{
std::string id;
void Do() { }
//add common behavior to all id classes
//...
};
struct myId : private BaseId
{
using BaseId::id;
};
Private inheritance allows your derived classes to control what to expose and what to not expose as well as preventing type compatibility between different id classes.
In the base class you can log, process, provide additional methods and so on. Implementing the operator overloads can be done in base class as well.
Is it required for your Id? I don't know. Might be overkill, might not. But it is worth to take such options into account. As I stated early, take the simple approach that works well in your case (even some typedefs might suffice, if not, structures would behave well).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With