I want a class to store the name of a team. I could use std::string for this but it is not really semantically correct. An std::string means any string while my team name can't be either empty or longer than 10 characters.
class TeamName {
public:
TeamName(std::string _teamName) : teamName(std::move(_teamName))
{
if (teamName.length() == 0 || teamName.length() > 10) {
throw std::invalid_argumet("TeamName " + teamName + " can't be empty or have more than 10 chars." );
}
}
private:
std::string teamName;
};
I like this: I'm checking the integrity of the name only once, future developers that never heard of this requirement won't be able to propagate invalid names from their new functions, there is an obvious place in the program logic to place exception handlers, etc.
But this construction means I have to use getters and setters to interact with the name, which pollutes the code a lot. If I inherit from std::string, however, the code barely changes and I can treat TeamName exactly as an std::string
class TeamName : public std::string {
public:
TeamName(std::string _teamName) : std::string(_teamName)
{
if (length() == 0 || length() > 10) {
throw std::invalid_argumet("TeamName " + teamName + " can't be empty or have more than 10 chars." );
}
}
};
Is this bad practice? It feels conceptually correct for me.
What's the way to achieve the consistency I want and still be able to use the wrapper no different from how I use a string?
std::string class in C++ C++ has in its definition a way to represent a sequence of characters as an object of the class. This class is called std:: string. String class stores the characters as a sequence of bytes with the functionality of allowing access to the single-byte character.
std::string - the C++ String Class. C++ provides a simple, safe alternative to using char*s to handle strings. The C++ string class, part of the std namespace, allows you to manipulate strings safely.
In C++17, you could use std::string_view
as target type of an implicit conversion instead:
class TeamName {
std::string val;
public:
TeamName(std::string name): val(std::move(name)) { /* snip */ }
operator std::string_view() const {
return val;
}
};
This lets you use TeamName
everywhere you could use a std::string_view
, while disallowing modification (so your invariant on the length holds after object creation). Of course, that requires the parts of your code that should consume the object to know about std::string_view
.
If std::string_view
is not feasible for you, you could define the conversion to const std::string&
instead.
One potential problem with the inheritance is that it lets people treat your TeamName
as a normal std::string
. Example:
class TeamName : public std::string {
public:
TeamName(std::string _teamName) : std::string(_teamName)
{
if (length() == 0 || length() > 10) {
throw std::invalid_argument("TeamName " + *this + " can't be empty or have more than 10 chars.");
}
}
};
void appendToString(std::string& s)
{
s.append(" this is a long text, and the resulting teamname is no longer valid...");
}
int main()
{
TeamName t = std::string("CoolTeam");
std::cout << "The teamname is: " << t << std::endl;
appendToString(t);
std::cout << "And now it is: " << t << std::endl;
}
Output:
The teamname is: CoolTeam
And now it is: CoolTeam this is a long text, and the resulting teamname is no longer valid...
It is possible to protect your inherited TeamName
from all illegal changes - just overload .append
and all other functions that can do anything to invalidate it. But with inheritance the problem becomes finding all the functions that have to be checked/restricted, and then writing an overload that handles it.
EDIT: Not true, as pointed out in the comments, member functions in std::string
are not marked as virtual
. This means that if you access a TeamName
through a pointer or ref to std::string
then you will get the std::string
implementation, and no amount of overloading will be able to protect your TeamName
from modifications.
On the other hand, with a private string people can only touch your data through the functions you let them access.
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