class String : public std::string {}; ????

A

Angus Leeming

Hello,

Could someone explain to me why the Standard conveners chose to typedef
std::string rather than derive it from std::basic_string<char, ...>?

The result of course is that it is effectively impossible to forward declare
std::string. (Yes I am aware that some libraries have a string_fwd.h header,
but this is not portable.)

That said, is there any real reason why I can't derive an otherwise empty
String class from std::string? Will there be any hidden surprises? As I
understand it, the fact that String has no member variables of its own means
that the non-virtual std::string destructor is not an issue.

Nonetheless, I'm wary because I have heard so much about 'std::string is not
designed to be derived from'. Nobody has explained the rationale behind such
a statement to me however.

Below is sample code that appears to work just fine.

Best regards,
Angus



#ifndef FWD_DECLARABLE_STRING_H
#define FWD_DECLARABLE_STRING_H

#include <string>

struct String : public std::string {
typedef std::string base_type;

String() {}
String(base_type const & other)
: base_type(other) {}
explicit String(allocator_type const & al)
: base_type(al) {}
String(String const & other, size_type off, size_type count = npos)
: base_type(other, off, count) {}
String(String const & other, size_type off, size_type count,
allocator_type const & al)
: base_type(other, off, count, al) {}
String(value_type const * ptr, size_type count)
: base_type(ptr, count) {}
String(value_type const * ptr, size_type count, allocator_type const & al)
: base_type(ptr, count, al) {}
String(value_type const * ptr)
: base_type(ptr) {}
String(value_type const * ptr, allocator_type const & al)
: base_type(ptr, al) {}
String(size_type count, value_type ch)
: base_type(count, ch) {}
String(size_type count, value_type ch, allocator_type const & al)
: base_type(count, ch, al) {}
template <typename InIt>
String(InIt first, InIt last)
: base_type(first, last) {}
template <typename InIt>
String(InIt first, InIt last, allocator_type const & al)
: base_type(first, last, al) {}

String & operator=(value_type const * ptr)
{ base_type::eek:perator=(ptr); }
String & operator=(value_type ch)
{ base_type::eek:perator=(ch); }
};

#endif // FWD_DECLARABLE_STRING_H



#include <iostream>

int main()
{
String const angus = "angus";
std::string const jmarc = "jmarc";
String test1;
std::string test2;

// Assign a std::string to a String
test1 = jmarc;
std::cout << "test1 " << test1 << std::endl;

// Assign a String to a std::string
test2 = angus;
std::cout << "test2 " << test2 << std::endl;

// Assign a String to a String (implicit assignment operator)
test1 = angus;
std::cout << "test1 " << test1 << std::endl;

// Implicit copy c-tor;
String const test3 = angus;
std::cout << "test3 " << test3 << std::endl;

// Copy construct a std::string from a String
std::string test4 = angus;
std::cout << "test4 " << test4 << std::endl;

return 0;
}
 
R

Ron Natalie

Angus Leeming said:
That said, is there any real reason why I can't derive an otherwise empty
String class from std::string? Will there be any hidden surprises? As I
understand it, the fact that String has no member variables of its own means
that the non-virtual std::string destructor is not an issue.

What makes you think it's not an issue? It is undefined behavior to delete
a derived object from a pointer to its base class without a virtual distructor.
It doesn't matter that the derived destructor doesn't do anything or that there
are no data members in the derived class.
 
L

llewelly

Angus Leeming said:
Hello,

Could someone explain to me why the Standard conveners chose to typedef
std::string rather than derive it from std::basic_string<char, ...>?

The result of course is that it is effectively impossible to forward declare
std::string. (Yes I am aware that some libraries have a string_fwd.h header,
but this is not portable.)

That said, is there any real reason why I can't derive an otherwise empty
String class from std::string?

There are two issues.

(a) No virtual destructor. This means you can't safely call delete on
a pointer to string when the object pointed to is in fact a
derived_from_string.

(b) It isn't designed for polymorphism. (Like most of the standard
library.) Public inheritance is usually used for
polymorphism. Note (a) is a symptom of (b) .

If you somehow know you'll never use the derived_from_string
polymorphically, you won't encounter problems.

However, before proceeding, try private inheritance combined with a
few well-placed using statements. That is safer.

Will there be any hidden surprises?

See above. Further, if people *can* make polymorphic use of your
derived_from_string type, they all too often will.
As I understand it, the fact that String has no member variables of
its own means that the non-virtual std::string destructor is not an
issue.
[snip]

This has nothing to do with it. Calling delete on a pointer to string
is undefined behavior if it in fact points to a
derived_from_string, regardless of whether derived_from_string has
member variables of its own.
 
A

Angus Leeming

Ron said:
What makes you think it's not an issue? It is undefined behavior to delete
a derived object from a pointer to its base class without a virtual
distructor. It doesn't matter that the derived destructor doesn't do
anything or that there are no data members in the derived class.

Many thanks to both of you for the clear explanation. I shall put your
arguments to the project moderator who will promptly boot my proposed use of
String into touch ;-)

Thanks again,
Angus
 
I

Ivan Vecerina

Hi Angus,

| Could someone explain to me why the Standard conveners chose to typedef
| std::string rather than derive it from std::basic_string<char, ...>?

Two reasons I can see:
- If this were done, template code that uses std::basic_string<ACharType>
would be using a different string class than your non-template code ?
Or some special policy classes would be needed...
- If such derivation was used, std::basic_string would need a virtual
destructor.

| The result of course is that it is effectively impossible to forward
declare
| std::string. (Yes I am aware that some libraries have a string_fwd.h
header,
| but this is not portable.)
True. Note that adding such a header yourself is an option even for
implementations that do not support it...

| That said, is there any real reason why I can't derive an otherwise empty
| String class from std::string? Will there be any hidden surprises? As I
| understand it, the fact that String has no member variables of its own
| means that the non-virtual std::string destructor is not an issue.
It *might* not be an issue in most C++ implementations. But formally,
it still does lead to undefined behavior when used improperly.
The following related discussion might be of interest:
http://www.gotw.ca/publications/mill18.htm


| Nonetheless, I'm wary because I have heard so much about 'std::string is
not
| designed to be derived from'. Nobody has explained the rationale behind
such
| a statement to me however.

The public non-virtual destructor remains the key issue.
Another caveat is the too many member functions of std::string,
which may at some point create conflicts...

Most of the time, people who want to derive from std::string also want to
add some state/data members to it (in which case containment is a better
idea, as it allows to enforce class invariants), or add some member
functions (in that case, non-member functions should be preferred).
Public derivation from std::string has serious caveats,
and is often done by novices for these wrong reasons.
And indeed, I have yet to see a good motivation for deriving from
std::string...

| Below is sample code that appears to work just fine.
From a quick look through the code it seems ok and will not invoke UB.
However, is it worth defining your own class just to be
able to forward-declare it ?
Force any maintainers to use an additional non-standard class,
and deal with (even implicit) conversions ?

Personally, if compile-time overhead was a read problem,
would rather implement a custom <string_fwd.h> header for
each platform I am using...


Regards,
Ivan
 
A

Angus Leeming

Ivan said:
| That said, is there any real reason why I can't derive an otherwise empty
| String class from std::string? Will there be any hidden surprises? As I
| understand it, the fact that String has no member variables of its own
| means that the non-virtual std::string destructor is not an issue.
It *might* not be an issue in most C++ implementations. But formally,
it still does lead to undefined behavior when used improperly.
The following related discussion might be of interest:
http://www.gotw.ca/publications/mill18.htm

Thanks for the pointer.
Most of the time, people who want to derive from std::string also want to
add some state/data members to it (in which case containment is a better
idea, as it allows to enforce class invariants), or add some member
functions (in that case, non-member functions should be preferred).
Understood.

Personally, if compile-time overhead was a read problem,
would rather implement a custom <string_fwd.h> header for
each platform I am using...

Guess what my next proposal to the project is going to be ;-)

Many thanks to you for your time; it is much appreciated.
Regards,
Angus
 
T

tom_usenet

Hello,

Could someone explain to me why the Standard conveners chose to typedef
std::string rather than derive it from std::basic_string<char, ...>?

The result of course is that it is effectively impossible to forward declare
std::string. (Yes I am aware that some libraries have a string_fwd.h header,
but this is not portable.)

Why do you need to forward declare string? Have you measured an actual
increase in compile time when you #include <string>?

Forward declarations are generally used to decouple code and reduce
dependencies. However, a dependency on std::string isn't a dependency
at all, it is just a use of the language. IOW, since std::string only
changes if you upgrade your compiler (in which case a full recompile
is required anyway), there isn't much to be gained from a forward
declaration.
That said, is there any real reason why I can't derive an otherwise empty
String class from std::string? Will there be any hidden surprises? As I
understand it, the fact that String has no member variables of its own means
that the non-virtual std::string destructor is not an issue.

In practice this is true on most compilers. However, it is dangerous
to rely on it since the standard forbids it.
Nonetheless, I'm wary because I have heard so much about 'std::string is not
designed to be derived from'. Nobody has explained the rationale behind such
a statement to me however.

The idea is that there is generally nothing to be gained from deriving
from std::string, and there is danger (the non-virtual destructor). If
you need to:

1. Add features.
Add them as non-member functions.
2. Add members.
Create a class that contains a string and your extra members or use
private inheritence - typically you won't want the full string
interface anyway (often combined with 3).
3. Add invariants.
You can't use public derivation for this, you must use private
inheritence or containment, since otherwise the invariants can be
broken by accessing the object as a string&.
4. Override behaviour.
Inheritence doesn't work since none of the methods are virtual.
5. Forward declare String (!)
Just #include <string>.

Tom
 
A

Angus Leeming

tom_usenet said:
Why do you need to forward declare string? Have you measured an actual
increase in compile time when you #include <string>?

That's exactly what I am going to do. I am talking about a project (LyX,
www.lyx.org) that takes 26mins on a 2.7GHz machine to do a full compile. It
gets a little wearing at times, so we all start hunting around for
scapegoats. Yeh, I know. We should write decent code in the first place ;-)

Regards,
Angus
 
?

=?iso-8859-1?Q?Andr=E9_P=F6nitz?=

llewelly said:
There are two issues.

(a) No virtual destructor. This means you can't safely call delete on
a pointer to string when the object pointed to is in fact a
derived_from_string.

(b) It isn't designed for polymorphism. (Like most of the standard
library.) Public inheritance is usually used for
polymorphism. Note (a) is a symptom of (b) .

If you somehow know you'll never use the derived_from_string
polymorphically, you won't encounter problems.

Is there a way to make sure that a class will never be used for
inheritance? [I don't think so but I thought I better ask]
However, before proceeding, try private inheritance combined with a
few well-placed using statements. That is safer.

This would make both 'issues' go away, wouldn't it?

So to re-phrase Angus' original question: Why is std::string a typedef
and not _privately_ derived from std::basic_string<char> with a lots of
'using' in it? [The benefit would still be there: to be able to
forward-declare it]

Or, alternatively:

Why isn't there a 'slim' Standard <stringfwd> header?

Andre'
 
T

tom_usenet

That's exactly what I am going to do. I am talking about a project (LyX,
www.lyx.org) that takes 26mins on a 2.7GHz machine to do a full compile. It
gets a little wearing at times, so we all start hunting around for
scapegoats. Yeh, I know. We should write decent code in the first place ;-)

I don't think GCC supports pre-compiled headers, which might help for
system and standard headers. GCC is a very slow compiler though at the
best of times.

Otherwise, it's just a matter of reducing the real dependencies in
your code, so less of your own headers are included in each file.

Tom
 
S

Shane Beasley

André Pönitz said:
Is there a way to make sure that a class will never be used for
inheritance? [I don't think so but I thought I better ask]
http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.8
http://www.research.att.com/~bs/bs_faq2.html#no-derivation
However, before proceeding, try private inheritance combined with a
few well-placed using statements. That is safer.

This would make both 'issues' go away, wouldn't it?

Mostly, yes. A private inheritance relationship is known only to the
derived class and its friends, so only they can use it
polymorphically. If they don't use it polymorphically, there is no
problem.
So to re-phrase Angus' original question: Why is std::string a typedef
and not _privately_ derived from std::basic_string<char> with a lots of
'using' in it? [The benefit would still be there: to be able to
forward-declare it]

A few reasons. First and foremost, it wouldn't be a std::basic_string
instantation anymore, so it wouldn't work with templates that expect
std::basic_string. (I'm sure I could come up with other forward
declarations that might do worse.)

Second, it's a hack. I've used private inheritance and using
declarations to implement a new class in terms of an old one. But I'd
never recommend using them to fake forward declarations, certainly not
at the level of the Standard.
Or, alternatively:

Why isn't there a 'slim' Standard <stringfwd> header?

Since we've got <iosfwd>, my guess is that they just forgot to put one
in. Sounds like something to be asked and/or debated on comp.std.c++.
:)

---

Incidentally, you can (depending on your intestinal fortitude)
forward-declare these things yourself. Herb Sutter tells us not to do
it in GOTW 34:

<quote url="http://www.gotw.ca/gotw/034.htm">
The standard says:

It is undefined for a C++ program to add declarations or definitions
to namespace std or namespaces within namespace std unless otherwise
specified.
</quote>

Of course, in the Real World, without which ISO C++ is meaningless,
I'd love to see an implementation which does anything worse than fail
to compile on a forward-declaration of std::string.

Herb continues:

<quote>
Among other things, this allows vendors to provide implementations of
the standard library that have more template parameters for library
templates than the standard requires (suitably defaulted, of course,
to remain compatible).
</quote>

But according to the official ISO C++ closed issues list, they can't:

<quote url="http://std.dkuug.dk/jtc1/sc22/wg21/docs/lwg-closed.html#94">
Library implementors are not permitted to add template parameters to
standard library classes.
</quote>

So, any compiler that doesn't supply the *exact* template interface is
non-conforming. If we know the interface, we ought to be able to
forward-declare it, like so:

namespace std {
template <typename> class char_traits;
template <typename> class allocator;

// cannot use default parameters in the forward declaration
template <typename, typename, typename> class basic_string;

// instead, use them here
typedef std::basic_string<char,
std::char_traits<char>,
std::allocator<char> > string;
}

But since this is heresy in comp.lang.c++, I suppose you're left to
including <string> whenever you need std::string, at least for now.

Better go get some coffee... This could take awhile. :)

- Shane
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top