5

std::string (as most — if not all — standard classes) doesn’t have any virtual methods, so creating an inherited class with virtual methods will result in UB (due most likely to the destructor). (correct me if I am wrong).

I thought that inheriting without polymorphism it’s ok though, until I read upon the net on this subject.

For instance, in this answer: Why should one not derive from c++ std string class? some arguments are given against this practice. The main reason seems to be the slicing problem which will inhibit the added functionality when the derived object is passed to a function in place of a std::string parameter, thus making the non-polymorphism not logical. The idiomatic C++ way is to create free functions if one wants to extend the functionality of string. And I agree with all that, especially since I am an advocate for free functions instead of monolithic classes.


That being said I think that I found a situation that I think actually warrants the non-polymorphic inheritance from std::string. I will first show what issue I am trying to solve, then I will show the reasons why I think inheriting from std::string is the best solution here.

When porting a function used for debugging purposes from C to C++ I realised there is no way to create formatted strings in C++ (not using C-like string functions e.g. sprintf). I.e.:

C version: void someKindOfError(const char *format, ...);
this would be called like:

someKindOfError("invalid argument %d, size = %d", i, size);

C++ version: void someKindOfError(const std::string &message);
to call this to a similar effect would be:

std::stringstream ss;
ss << "invalid argument " << i << ", size = " << size;
someKindOfError(ss.str());

this can’t be a one liner because the << operator returns an ostream. So this requires 2 extra lines and an extra variable.

The solution I came up with is a class called StreamString that inherits from std::string (actually a templated BasicStreamString that inherits from basic_string<>, but that’t not important) and as far as the new functionality goes it has the operator << that mimics the behaviour of the stringstream operator and conversion to and from string.

So the previous example can become:

someKindOfError(StreamString() << "invalid argument " << i << ", size = " << size);

remember that the parameter type is still const std::string &

The class is created and fully functional. And I found this class very useful in a lot of places when a string is need to be created ad-hoc, without the extra burden of having to declare an extra stringstream variable. And this object can be manipulated further like a stringstream, but it is actually a string and can be passed to functions expecting string.


Why I think this is an exception to the C++ idiom:

  • the object needs to behave exactly like a string when passed to functions expecting a string, so the slicing problem is not an issue.
  • the only (noteworthy) added functionality is the operator << which I am reluctant to overload as a free function for a standard string object (this would be done in a library).

One alternative I can think of is to create a variadic templated free function. Something like:

template <class... Args>
std::string createString(Args... args);

which allow us to call like this:

someKindOfError(createString("invalid argument ", i , ", size = " , size));

One disadvantage of this alternative is the lost of the ability to easily manipulate the string like an stringstream after it’s creation. But I suppose I can create a free function to handle that too. Also people are use with the operator << to perform formatted inserts.


To round up:

  • Is my solution bad practice (or worst) or it is an exception to the C++ idiom and it is OK?
  • If it is bad, what viable alternatives are there? Is createString ok? Can it be improved?
Community
  • 1
  • 1
bolov
  • 72,283
  • 15
  • 145
  • 224
  • Creating a derived class with virtual member functions does not necessarily result in UB. Deleting an instance of the derived class through a pointer to a base class does result in UB whether or not the derived class has virtual member functions. Also, your `StreamString` does not need to derive from `std::string` in order to be used that way. It's enough for you to provide a conversion operator to `std::string` or `char const*`. – Andy Prowl May 24 '14 at 18:03
  • 1
    @HansPassant: There is the `final` (contextual) keyword in C++11. – Andy Prowl May 24 '14 at 18:04
  • Yes, finally :) Too late for std::string. – Hans Passant May 24 '14 at 18:06
  • Why are you making an operator << on a string? Your 'someKindOfError' is a stream, actually! Hence, I would answer: do not do it. –  May 24 '14 at 18:19

2 Answers2

4

You don't need to derive a class from std::string for this. Just create an unrelated class, say StringBuilder that keeps a std::stringstream internally. Overload operator << for this class and add a std::string cast operator.

Something like this should do the trick (untested):

class StringBuilder
{
    std::ostringstream oss;

public:
    operator std::string() const
    {
        return oss.str();
    }

    template <class T>
    friend StringBuilder& operator <<(StringBuilder& sb, const T& t)
    {
        sb.oss << t;
        return *this;
    }
};
D Drmmr
  • 1,223
  • 8
  • 15
  • +1. I have some code very similar to this that gets used a lot for debugging. – Jason R May 24 '14 at 18:30
  • 1
    This is nothing more than a std::ostringstream with a conversion operator. (I fear there is no gain - not even in convenience) –  May 24 '14 at 18:40
  • 2
    @DieterLücking: It is a little more than that. The `operator<<` in `std::ostringstream` returns an `ostream &`, so the return value must be cast back to an `ostringstream &` before `.str()` can be called on it. – Mankarse May 24 '14 at 18:45
  • the reason I used inheritance instead of an unrelated class was that I wouldn't have to rewrite all the string interface (like size, operator[], lexicographically comparison operators etc.). Would a bare bone class like this just with the option to extract the string be viable (instead of providing a string interface)? – bolov May 24 '14 at 18:46
  • @DieterLücking Yes, this is nothing but some sugar coating. That's what the question asked for. – D Drmmr May 24 '14 at 19:16
  • @bolov How would you use the string interface? From your question it seems that all you want is to build a string using stream interface and pass it to a function all in a single line. – D Drmmr May 24 '14 at 19:17
  • Yes, that’s what I wanted initially. I just thought that having a string in which you can append variable values would be extra useful. – bolov May 24 '14 at 19:47
  • @bolov: Think about this in natural language. What you want is a mechanism to create a string, not a string, do you? – David Rodríguez - dribeas May 24 '14 at 20:08
1

Your StreamString class is OK, in that there do not seem to be any situations where normal usage of it could get you into trouble. Even so, there are a lot of alternatives that might be more appropriate to this situation.

  1. Use a pre-existing library, such as Boost.Format, rather than rolling your own. This has the advantage of being widely known, tested, supported, etc...

  2. Write someKindOfError to be a variadic template, to match the C version, but with added C++ type-safety goodness. This has the advantage of matching the C version, and so being familiar to your existing users.

  3. Give StringStream a conversion operator or an explicit to_string function, rather than inheriting from std::string. This gives you more flexibility to change the implementation of the StringStream at a later stage. (For example, at a later stage, you might decide that you want to use some kind of cacheing or buffering scheme, but this would be impossible if you do not know exactly when the final string will be extracted from the StringStream).

    As it is, your design is conceptually flawed. The only thing that you need is the ability to convert a StringStream to a std::string. Inheritance is an overly heavy-handed way of achieving that goal, when compared to using a conversion operator.

  4. Write your original stringstream code as a one-liner:

someKindOfError(static_cast<std::stringstream &>(
    std::stringstream{} << "invalid argument " << i << ", size = " << size).str());

... well, that's pretty ugly, so maybe not. You should consider it though, if your only reason for not doing this was that you thought it was not possible.

Community
  • 1
  • 1
Mankarse
  • 39,818
  • 11
  • 97
  • 141
  • the reason I used Inheritance instead of an unrelated class with conversion operators (before I thought it might be a problem) was that I wouldn't have to rewrite all the `string` interface (like `size`, `operator[]`, lexicographically comparison operators etc.). Would a bare bone class just with the option to extract the string be viable (instead of providing a string interface)? – bolov May 24 '14 at 18:43
  • @bolov: Where do you use the `string` interface in the call to `someKindOfError`? More generally - where and why do you need a `StringStream` to have a string interface? It seems that giving it a string interface would be a disadvantage rather than an advantage. – Mankarse May 24 '14 at 18:46
  • the idea initially started because I wanted to pass strings that were dependent to some variable values to functions receiving std::string (like my `someKindOfError` example), but now I think of it like something that has the best of `string` and `stingstream` – bolov May 24 '14 at 18:49
  • A `string` and an `ostream` are fundamentally different. A string allows arbitrary indexing and modification of characters anywhere inside a character buffer, while an `ostream` allows the appending of the formatted version of some object onto the end (and only the end) of a character-buffer. Having separate classes for those two cases provides a separation of concerns, allowing more flexibility in the implementation of both of the classes, and clarifying the meaning of code that uses them. – Mankarse May 24 '14 at 19:04
  • If you really do think that an "arbitrarily modifiable character-buffer that also provides formatted insertions at its end" is a useful concept, then you *still* shouldn't inherit from `std::string`, because doing so needlessly limits your implementation options, with the only advantage being that it saves you a bit of typing. – Mankarse May 24 '14 at 19:05
  • Inheritance-to-get-an-implementation is only really useful when the classes that you are inheriting from are designed to be used in that way, such as small classes with no state (like `std::true_type`), CRTP base classes (though even these are somewhat questionable), or classes that are part of the private implementation of a type that cannot be implemented in any other way (like some of the classes that exist in the implementation of `std::tuple`). – Mankarse May 24 '14 at 19:12