Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it ok to inherit from std::string to provide type consistency?

Tags:

c++

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?

like image 549
Felipe Coimbra Avatar asked Jan 23 '20 08:01

Felipe Coimbra


People also ask

Why do we use std :: 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.

Is std :: string safe?

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.


2 Answers

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.

like image 154
flyx Avatar answered Oct 19 '22 23:10

flyx


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.

like image 38
Frodyne Avatar answered Oct 20 '22 01:10

Frodyne