tags:

views:

297

answers:

8

The class std::wstring is missing some operations for "normal" c strings (and literals).

I would like to add these missing operations in my own custom class:

#include <string>

class CustomWString : public std::wstring {

        public:

            CustomWString(const char*);
            const char* c_str(void);
};

The code above compiles fine on Ubuntu Karmic with g++ v4.4.1.

But I am wondering if there are arguments against doing so?

EDIT: some examples to clarify what I mean with "missing operations" :

std::wstring foo("hello"); // requires the L() macro or something like that
std::string bar("hello");
std::wstring goose(bar.c_str());

foo="hello";  // requires the L() macro
foo=bar.c_str();
foo=bar;

EDIT: I would like to have this somehow "centralized". That's because I have a project to be ported from M$-Windows with thousand's of these failing operations.

The nice thing is: there is one central place where the string type to be used is defined, e.g.:

#ifdef WINDOWS_OS
    typedef std::wstring AppStringClass;
#else
    typedef std::string AppStringClass;
#endif
+2  A: 

The class template std::basic_string (of which std::wstring is a typedef with specific template arguments) does not have a virtual destructor, and as such, inheriting from it publicly is a bad idea.

James McNellis
+13  A: 

It is legal, but many people think it undesirable. The issue is that the standard library template classes do not have virtual destructors. So if you ever write code like this:

std::wstring * p = new CustomWString ;
...
delete p;

you have undefined behaviour.

anon
+1  A: 

You can do it. But I see no point, since std::basic_string (and its concrete versions std::string and std::wstring) define no virtual methods and have non-virtual destructor. So there is no way, you can use your subclass properly in place of e.g. std::wstring.

Tomek Szpakowicz
+3  A: 

I think you know that basic_string doesn't define a virtual destructor. It is WRONG to use wstring in a polymorphic manner. Otherwise, you can use your custom class as usual.

So, the following is hazardous:

vector<wstring*> mystrings;
mystrings.push_back(new CustomWString);
...
delete mystrings[0]; // No virtual destructor

Using your custom class directly is OK, e.g.

vector<CustomWString> mystrings; // OK
mystrings.push_back("...");
AraK
+2  A: 

The traditional argument against inheriting from STL objects is they do not have a virtual destructor. So, take vector for example. If I were to inherit from it:

  class CMyVect : public std::vector<int>
  {
      ...
     int* someMemory;
  public:
     CMyVect()
     {
        someMemory = new int();
     }

     ~CMyVect()
     {
        delete someMemory;
     }
  };

The non virtual destructor in the base class means that if the user of this points a ponter-to-vector-of-ints to a CMyVect then deletes that pointer, your destructor will not be invoked, leaking memory. That is to say:

 int main()
 {
     std::vector<int>* vecPtr = new CMyVect();
     delete vecPtr; // invokes vector constructor only, not yours
 }

So that's not to say you can't inherit from these objects, but it can be frought with this problem. You have to be careful how you use the objects you are inheriting from or alternatively j ust have no need for your destructor to fire (don't hold any resources that need to be freed).

Doug T.
Lack of a virtual destructor is only only a problem when you introduce new data members. (That's not guaranteed by the standard, but a compiler implementation would have to go out of its way to break this)
peterchen
@peterchen: suppose that the derived class has one or more virtual members, and the base class doesn't. Then I'm pretty sure deleting through the base class will go wrong, because (in a typical implementation) it doesn't point to the start of a memory allocation, but the block will be freed as if it did. The compiler isn't going out of its way. I think it's best to advise just to avoid undefined behaviour, rather than try to figure out in what conditions a "reasonable" compiler will in fact do something predictable.
Steve Jessop
(Also, the questioner's example does have a new data member).
Steve Jessop
@peterchen the standard explicitly says that the lack of a virtual destructor will cause undefined behaviour if a derived object is deleted via a base pointer. The fact that it appears to work does not mean that the behaviour is not undefined.
anon
@Steve: I'd consider introducing a VMT a new data member in that context - as it changes the in-memory representation. .... @Neil: that doesn't mean every class needs a virtual DTor. I agree that we are beyond what's sanctioned by the compiler, though.
peterchen
+8  A: 

The compiler definitely lets you, as there is no way to seal a class with a public constructor in C++.

STL containers are not designed for inheritance, so it's generally a bad idea. C++ has to many strange corners to ignore that. Your derived class might break with another STL implementation, or just with an update of the build environment.

However, I'd consider it a striong warning sign, not a total no-go. It's a "are you really really sure what you are doing, do you know all implications, and have good arguments against all alternatives"?

In your case: Providing additional constructors is fine (since the public interface is documented and you can replicate that), and as long as you are not introducing new data members or a VMT, slicing or non-virtual destructor is not a problem. (Keep in mind though that the C++ standard claim undefined behavior when deleting through a base class pointer without virtual DTor)

However I am not sure how you want to implement char const * c_str() without also providing storage for the string. And as soon as you introduce new data members, you are entering a field of landmines.

peterchen
you are right regarding "how to implement c_str() without providing local storage".
+2  A: 

It is possible, and legal. But it's a bad idea. The classes are not designed for inheritance, and extending them is best done without inheritance. If you are not careful when using the classes, you'll get undefined behavior. (The classes don't have virtual destructors)

Moreover, it seems a bad idea to add those specific functions, since they depend on locale and encoding. You're giving the class more than one responsibility. The functionality of converting between char and wchar_t belongs elsewhere.

jalf
A: 

The ability to "inherit" one class from another is a language feature. It is just a formal, plain and dry language feature that is not tied to any specific aplication or any specific use model. It is a low-level tool that you can use for different purposes. And, to answer your question, it is perfectly legal to inherit from std::wstring class.

However, the next question would be the question of "why". Why do you want to inherit from std::wstring?

If you are designing your code within the OOP paradigm, then inheriting publicly from std::wstring might not be a good idea, "might" being a key word here. Public inheritance in OOP usually implies an IS-A relationship in a polymorphic class hierarchy. Class std::wstring is not designed to be polymorphic, which is the reason why publicly inheriting from it might be seen as a rather strange thing to do.

If you are designing your code within a different paradigm, like for example template meta-programming one(TMP), then public inheritance might be a perfectly valid thing to do. You see, in TMP public inheritance serves a completely different purpose, not even remotely related to polymorphism and relationships.

In other words, there no way to produce a concrete answer to your question without taking into account the bigger picture. In any case, beware of "mechanical" answers, like "since std::wstring has no virtual destructor, you can inherit from it since it will break ". Such reasoning is completely bogus, even within the OOP approach.

AndreyT