Why should one not derive from c++ std string class?
Solution 1
I think this statement reflects the confusion here (emphasis mine):
I do not understand what specifically is required in a class to be eligible for being a base clas (not a polymorphic one)?
In idiomatic C++, there are two uses for deriving from a class:
- private inheritance, used for mixins and aspect oriented programming using templates.
-
public inheritance, used for polymorphic situations only. EDIT: Okay, I guess this could be used in a few mixin scenarios too -- such as
boost::iterator_facade
-- which show up when the CRTP is in use.
There is absolutely no reason to publicly derive a class in C++ if you're not trying to do something polymorphic. The language comes with free functions as a standard feature of the language, and free functions are what you should be using here.
Think of it this way -- do you really want to force clients of your code to convert to using some proprietary string class simply because you want to tack on a few methods? Because unlike in Java or C# (or most similar object oriented languages), when you derive a class in C++ most users of the base class need to know about that kind of a change. In Java/C#, classes are usually accessed through references, which are similar to C++'s pointers. Therefore, there's a level of indirection involved which decouples the clients of your class, allowing you to substitute a derived class without other clients knowing.
However, in C++, classes are value types -- unlike in most other OO languages. The easiest way to see this is what's known as the slicing problem. Basically, consider:
int StringToNumber(std::string copyMeByValue)
{
std::istringstream converter(copyMeByValue);
int result;
if (converter >> result)
{
return result;
}
throw std::logic_error("That is not a number.");
}
If you pass your own string to this method, the copy constructor for std::string
will be called to make a copy, not the copy constructor for your derived object -- no matter what child class of std::string
is passed. This can lead to inconsistency between your methods and anything attached to the string. The function StringToNumber
cannot simply take whatever your derived object is and copy that, simply because your derived object probably has a different size than a std::string
-- but this function was compiled to reserve only the space for a std::string
in automatic storage. In Java and C# this is not a problem because the only thing like automatic storage involved are reference types, and the references are always the same size. Not so in C++.
Long story short -- don't use inheritance to tack on methods in C++. That's not idiomatic and results in problems with the language. Use non-friend, non-member functions where possible, followed by composition. Don't use inheritance unless you're template metaprogramming or want polymorphic behavior. For more information, see Scott Meyers' Effective C++ Item 23: Prefer non-member non-friend functions to member functions.
EDIT: Here's a more complete example showing the slicing problem. You can see it's output on codepad.org
#include <ostream>
#include <iomanip>
struct Base
{
int aMemberForASize;
Base() { std::cout << "Constructing a base." << std::endl; }
Base(const Base&) { std::cout << "Copying a base." << std::endl; }
~Base() { std::cout << "Destroying a base." << std::endl; }
};
struct Derived : public Base
{
int aMemberThatMakesMeBiggerThanBase;
Derived() { std::cout << "Constructing a derived." << std::endl; }
Derived(const Derived&) : Base() { std::cout << "Copying a derived." << std::endl; }
~Derived() { std::cout << "Destroying a derived." << std::endl; }
};
int SomeThirdPartyMethod(Base /* SomeBase */)
{
return 42;
}
int main()
{
Derived derivedObject;
{
//Scope to show the copy behavior of copying a derived.
Derived aCopy(derivedObject);
}
SomeThirdPartyMethod(derivedObject);
}
Solution 2
To offer the counter side to the general advice (which is sound when there are no particular verbosity/productivity issues evident)...
Scenario for reasonable use
There is at least one scenario where public derivation from bases without virtual destructors can be a good decision:
- you want some of the type-safety and code-readability benefits provided by dedicated user-defined types (classes)
- an existing base is ideal for storing the data, and allows low-level operations that client code would also want to use
- you want the convenience of reusing functions supporting that base class
- you understand that any any additional invariants your data logically needs can only be enforced in code explicitly accessing the data as the derived type, and depending on the extent to which that will "naturally" happen in your design, and how much you can trust client code to understand and cooperate with the logically-ideal invariants, you may want members functions of the derived class to reverify expectations (and throw or whatever)
- the derived class adds some highly type-specific convenience functions operating over the data, such as custom searches, data filtering / modifications, streaming, statistical analysis, (alternative) iterators
- coupling of client code to the base is more appropriate than coupling to the derived class (as the base is either stable or changes to it reflect improvements to functionality also core to the derived class)
- put another way: you want the derived class to continue to expose the same API as the base class, even if that means the client code is forced to change, rather than insulating it in some way that allows the base and derived APIs to grow out of sync
- you're not going to be mixing pointers to base and derived objects in parts of the code responsible for deleting them
This may sound quite restrictive, but there are plenty of cases in real world programs matching this scenario.
Background discussion: relative merits
Programming is about compromises. Before you write a more conceptually "correct" program:
- consider whether it requires added complexity and code that obfuscates the real program logic, and is therefore more error prone overall despite handling one specific issue more robustly,
- weigh the practical costs against the probability and consequences of issues, and
- consider "return on investment" and what else you could be doing with your time.
If the potential problems involve usage of the objects that you just can't imagine anyone attempting given your insights into their accessibility, scope and nature of usage in the program, or you can generate compile-time errors for dangerous use (e.g. an assertion that derived class size matches the base's, which would prevent adding new data members), then anything else may be premature over-engineering. Take the easy win in clean, intuitive, concise design and code.
Reasons to consider derivation sans virtual destructor
Say you have a class D publicly derived from B. With no effort, the operations on B are possible on D (with the exception of construction, but even if there are a lot of constructors you can often provide effective forwarding by having one template for each distinct number of constructor arguments: e.g. template <typename T1, typename T2> D(const T1& x1, const T2& t2) : B(t1, t2) { }
. Better generalised solution in C++0x variadic templates.)
Further, if B changes then by default D exposes those changes - staying in sync - but someone may need to review extended functionality introduced in D to see if it remains valid, and the client usage.
Rephrasing this: there is reduced explicit coupling between base and derived class, but increased coupling between base and client.
This is often NOT what you want, but sometimes it is ideal, and other times a non issue (see next paragraph). Changes to the base force more client code changes in places distributed throughout the code base, and sometimes the people changing the base may not even have access to the client code to review or update it correspondingly. Sometimes it is better though: if you as the derived class provider - the "man in the middle" - want base class changes to feed through to clients, and you generally want clients to be able - sometimes forced - to update their code when the base class changes without you needing to be constantly involved, then public derivation may be ideal. This is common when your class is not so much an independent entity in its own right, but a thin value-add to the base.
Other times the base class interface is so stable that the coupling may be deemed a non issue. This is especially true of classes like Standard containers.
Summarily, public derivation is a quick way to get or approximate the ideal, familiar base class interface for the derived class - in a way that's concise and self-evidently correct to both the maintainer and client coder - with additional functionality available as member functions (which IMHO - which obviously differs with Sutter, Alexandrescu etc - can aid usability, readability and assist productivity-enhancing tools including IDEs)
C++ Coding Standards - Sutter & Alexandrescu - cons examined
Item 35 of C++ Coding Standards lists issues with the scenario of deriving from std::string
. As scenarios go, it's good that it illustrates the burden of exposing a large but useful API, but both good and bad as the base API is remarkably stable - being part of the Standard Library. A stable base is a common situation, but no more common than a volatile one and a good analysis should relate to both cases. While considering the book's list of issues, I'll specifically contrast the issues' applicability to the cases of say:
a) class Issue_Id : public std::string { ...handy stuff... };
<-- public derivation, our controversial usage
b) class Issue_Id : public string_with_virtual_destructor { ...handy stuff... };
<- safer OO derivation
c) class Issue_Id { public: ...handy stuff... private: std::string id_; };
<-- a compositional approach
d) using std::string
everywhere, with freestanding support functions
(Hopefully we can agree the composition is acceptable practice, as it provides encapsulation, type safety as well as a potentially enriched API over and above that of std::string
.)
So, say you're writing some new code and start thinking about the conceptual entities in an OO sense. Maybe in a bug tracking system (I'm thinking of JIRA), one of them is say an Issue_Id. Data content is textual - consisting of an alphabetic project id, a hyphen, and an incrementing issue number: e.g. "MYAPP-1234". Issue ids can be stored in a std::string
, and there will be lots of fiddly little text searches and manipulation operations needed on issue ids - a large subset of those already provided on std::string
and a few more for good measure (e.g. getting the project id component, providing the next possible issue id (MYAPP-1235)).
On to Sutter and Alexandrescu's list of issues...
Nonmember functions work well within existing code that already manipulates
string
s. If instead you supply asuper_string
, you force changes through your code base to change types and function signatures tosuper_string
.
The fundamental mistake with this claim (and most of the ones below) is that it promotes the convenience of using only a few types, ignoring the benefits of type safety. It's expressing a preference for d) above, rather than insight into c) or b) as alternatives to a). The art of programming involves balancing the pros and cons of distinct types to achieve reasonable reuse, performance, convenience and safety. The paragraphs below elaborate on this.
Using public derivation, the existing code can implicitly access the base class string
as a string
, and continue to behave as it always has. There's no specific reason to think that the existing code would want to use any additional functionality from super_string
(in our case Issue_Id)... in fact it's often lower-level support code pre-existing the application for which you're creating the super_string
, and therefore oblivious to the needs provided for by the extended functions. For example, say there's a non-member function to_upper(std::string&, std::string::size_type from, std::string::size_type to)
- it could still be applied to an Issue_Id
.
So, unless the non-member support function is being cleaned up or extended at the deliberate cost of tightly coupling it to the new code, then it needn't be touched. If it is being overhauled to support issue ids (for example, using the insight into the data content format to upper-case only leading alpha characters), then it's probably a good thing to ensure it really is being passed an Issue_Id
by creating an overload ala to_upper(Issue_Id&)
and sticking to either the derivation or compositional approaches allowing type safety. Whether super_string
or composition is used makes no difference to effort or maintainability. A to_upper_leading_alpha_only(std::string&)
reusable free-standing support function isn't likely to be of much use - I can't recall the last time I wanted such a function.
The impulse to use std::string
everywhere isn't qualitatively different to accepting all your arguments as containers of variants or void*
s so you don't have to change your interfaces to accept arbitrary data, but it makes for error prone implementation and less self-documenting and compiler-verifiable code.
Interface functions that take a string now need to: a) stay away from
super_string
's added functionality (unuseful); b) copy their argument to a super_string (wasteful); or c) cast the string reference to a super_string reference (awkward and potentially illegal).
This seems to be revisiting the first point - old code that needs to be refactored to use the new functionality, albeit this time client code rather than support code. If the function wants to start treating its argument as an entity for which the new operations are relevant, then it should start taking its arguments as that type and the clients should generate them and accept them using that type. The exact same issues exists for composition. Otherwise, c)
can be practical and safe if the guidelines I list below are followed, though it is ugly.
super_string's member functions don't have any more access to string's internals than nonmember functions because string probably doesn't have protected members (remember, it wasn't meant to be derived from in the first place)
True, but sometimes that's a good thing. A lot of base classes have no protected data. The public string
interface is all that's needed to manipulate the contents, and useful functionality (e.g. get_project_id()
postulated above) can be elegantly expressed in terms of those operations. Conceptually, many times I've derived from Standard containers, I've wanted not to extend or customise their functionality along the existing lines - they're already "perfect" containers - rather I've wanted to add another dimension of behaviour that's specific to my application, and requires no private access. It's because they're already good containers that they're good to reuse.
If
super_string
hides some ofstring
's functions (and redefining a nonvirtual function in a derived class is not overriding, it's just hiding), that could cause widespread confusion in code that manipulatesstring
s that started their life converted automatically fromsuper_string
s.
True for composition too - and more likely to happen as the code doesn't default to passing things through and hence staying in sync, and also true in some situations with run-time polymorphic hierarchies as well. Samed named functions that behave differently in classes that initial appear interchangeable - just nasty. This is effectively the usual caution for correct OO programming, and again not a sufficient reason to abandon the benefits in type safety etc..
What if
super_string
wants to inherit fromstring
to add more state [explanation of slicing]
Agreed - not a good situation, and somewhere I personally tend to draw the line as it often moves the problems of deletion through a pointer to base from the realm of theory to the very practical - destructors aren't invoked for additional members. Still, slicing can often do what's wanted - given the approach of deriving super_string
not to change its inherited functionality, but to add another "dimension" of application-specific functionality....
Admittedly, it's tedious to have to write passthrough functions for the member functions you want to keep, but such an implementation is vastly better and safer than using public or nonpublic inheritance.
Well, certainly agree about the tedium....
Guidelines for successful derivation sans virtual destructor
- ideally, avoid adding data members in derived class: variants of slicing can accidentally remove data members, corrupt them, fail to initialise them...
- even more so - avoid non-POD data members: deletion via base-class pointer is technically undefined behaviour anyway, but with non-POD types failing to run their destructors is more likely to have non-theoretical problems with resource leaks, bad reference counts etc.
- honour the Liskov Substitution Principal / you can't robustly maintain new invariants
- for example, in deriving from
std::string
you can't intercept a few functions and expect your objects to remain uppercase: any code that accesses them via astd::string&
or...*
can usestd::string
's original function implementations to change the value) - derive to model a higher level entity in your application, to extend the inherited functionality with some functionality that uses but doesn't conflict with the base; do not expect or try to change the basic operations - and access to those operations - granted by the base type
- for example, in deriving from
- be aware of the coupling: base class can't be removed without affecting client code even if the base class evolves to have inappropriate functionality, i.e. your derived class's usability depends on the ongoing appropriateness of the base
- sometimes even if you use composition you'll need to expose the data member due to performance, thread safety issues or lack of value semantics - so the loss of encapsulation from public derivation isn't tangibly worse
- the more likely people using the potentially-derived class will be unaware of its implementation compromises, the less you can afford to make them dangerous
- therefore, low-level widely deployed libraries with many ad-hoc casual users should be more wary of dangerous derivation than localised use by programmers routinely using the functionality at application level and/or in "private" implementation / libraries
Summary
Such derivation is not without issues so don't consider it unless the end result justifies the means. That said, I flatly reject any claim that this can't be used safely and appropriately in particular cases - it's just a matter of where to draw the line.
Personal experience
I do sometimes derive from std::map<>
, std::vector<>
, std::string
etc - I've never been burnt by the slicing or delete-via-base-class-pointer issues, and I've saved a lot of time and energy for more important things. I don't store such objects in heterogeneous polymorphic containers. But, you need to consider whether all the programmers using the object are aware of the issues and likely to program accordingly. I personally like to write my code to use heap and run-time polymorphism only when needed, while some people (due to Java backgrounds, their prefered approach to managing recompilation dependencies or switching between runtime behaviours, testing facilities etc.) use them habitually and therefore need to be more concerned about safe operations via base class pointers.
Solution 3
Not only is the destructor not virtual, std::string contains no virtual functions at all, and no protected members. That makes it very hard for the derived class to modify its functionality.
Then why would you derive from it?
Another problem with being non-polymorphic is that if you pass your derived class to a function expecting a string parameter, your extra functionality will just be sliced off and the object will be seen as a plain string again.
Solution 4
If you really want to derive from it (not discussing why you want to do it) I think you can prevent Derived
class direct heap instantiation by making it's operator new
private:
class StringDerived : public std::string {
//...
private:
static void* operator new(size_t size);
static void operator delete(void *ptr);
};
But this way you restrict yourself from any dynamic StringDerived
objects.
Solution 5
Why should one not derive from c++ std string class?
Because it is not necessary. If you want to use DerivedString
for functionality extension; I don't see any problem in deriving std::string
. The only thing is, you should not interact between both classes (i.e. don't use string
as a receiver for DerivedString
).
Is there any way to prevent client from doing
Base* p = new Derived()
Yes. Make sure that you provide inline
wrappers around Base
methods inside Derived
class. e.g.
class Derived : protected Base { // 'protected' to avoid Base* p = new Derived
const char* c_str () const { return Base::c_str(); }
//...
};
Sriram Subramanian
Updated on July 16, 2020Comments
-
Sriram Subramanian almost 4 years
I wanted to ask about a specific point made in Effective C++.
It says:
A destructor should be made virtual if a class needs to act like a polymorphic class. It further adds that since
std::string
does not have a virtual destructor, one should never derive from it. Alsostd::string
is not even designed to be a base class, forget polymorphic base class.I do not understand what specifically is required in a class to be eligible for being a base class (not a polymorphic one)?
Is the only reason that I should not derive from
std::string
class is it does not have a virtual destructor? For reusability purpose a base class can be defined and multiple derived class can inherit from it. So what makesstd::string
not even eligible as a base class?Also, if there is a base class purely defined for reusability purpose and there are many derived types, is there any way to prevent client from doing
Base* p = new Derived()
because the classes are not meant to be used polymorphically? -
Sriram Subramanian about 13 yearsi am talking about non polymorphic
-
Sriram Subramanian about 13 yearsa base class need not have virtual functions to derive from it. For code reusability it is common to derive from another class. The only restriction is that you should not use the class as a polymorphic one.
-
Bo Persson about 13 years@Siriam - True, but it is hard to see any reason for code reusability from a string. It already has too many member functions, so extending it further doesn't seem like the best idea. You should also consider that when textbooks say "never do this" they actually mean "you should hardly ever do this".
-
Billy ONeal about 13 years@Sriram: There's no reason to derive from a class in C++ unless you need to modify internal state of the base class. Any extension you'd do to
std::basic_string<CharT, Allocator<CharT>>
would never be touching internal members. Therefore there's no need for any deriving to occur. C++ has free functions just for adding this type of functionality. Use a free function for your extensions (e.g. asboost::algorithm::string
does) -- that's how this is done in ideomatic C++. -
Billy ONeal about 13 yearsYou can, but this really doesn't answer the OP's question.
-
Sriram Subramanian about 13 yearswhat about existing code that is not part of the standard? Those functions are not part of any standard algorithm or library. There could also be instances where you want to just extend more functionality to a base class and the base class also provides getter and setter methods. so you could add more functionalities to the derived and also affect the base members and get their values if required. Constraining inheritance only to polymorphism seems drastic to me.
-
Billy ONeal about 13 years@Sriram: Standard or no standard, you should not derive in C++ unless it's template metaprogramming or polymorphic. If the original class doesn't expose some functionality you need, then expose that in the original class. Don't create a new class in an attempt to circumvent the access control system. It may seem drastic to you, but that is how it is done in idiomatic C++. The language comes with free functions -- use them.
-
Billy ONeal about 13 yearsThis works until you have to turn around and pass your derived string to a method or something of that nature. What's wrong with non-member functions?
-
davka about 13 years@Billy: it does answer the OP second question, see "Also..."
-
davka about 13 years"There is absolutely no reason to publicly derive a class in C++ if you're not trying to do something polymorphic"?? what about factoring a common functionality, e.g.
class LogService {protected: Logger log; /* some init */ }; class A: public LogService {/*...*/};
? -
Billy ONeal about 13 years@davka: That should be done with composition instead. The bits that are common should be in a class or classes of their own.
-
davka about 13 years@billy - why, what's the advantage? My example does not justify deriving from
std::string
, but it seems that deriving for polymorphism only is too restrictive -
Billy ONeal about 13 years@davka: Two reasons: 1. because you will have the slicing problem on a regular basis. There's no reason to add this kind of pain unless you have to. 2. Because if you're trying to do this, you class likely is in violation of SRP, and should be split anyway. If you need to do something requiring internal access to the class, then add a member function to the original class. If you don't, then use composition or non-member functions. (For this reason I recommend doing this in C# and Java as well, even though slicing isn't an issue)
-
Billy ONeal about 13 years@davka: If you're doing something with a logging class, your interests will probably be better served with mixins or strategies rather than inheritance. E.g. you'd have a logger class, a class defining the log format, and a sink class (E.g. file/memory/event log, whatever). The logger class would contain an instance of the other two. Now the three classes have simpler responsibilities and you remove the need to ever mess with inheritance.
-
davka about 13 yearsI think that my example was not clear enough - my
LogService
class should be calledLogClient
- it does not implement the logging itself, but captures the commonality of all classes that wish to use logging, like private memberlog
that should be initialized by getting a logger from a factory. -
Billy ONeal about 13 years@davka: Then I don't see why any deriving should be going on there. Could you post a minimal code example that points out what you mean? (i.e. on github gist, pastebin, codepad, etc...)
-
Tony Delroy about 13 years"Functional" as presented is not a reason for not doing it, but a comment that it's not necessary. That said, while C++'s forwarding "capabilities" make composition possible, it's a right pain initially and a maintainence burden thereafter which is why so many people want to consider, and do use (with varying degrees of safety) a derivation approach....
-
Tony Delroy about 13 years@Billy: non-member functions are workable, but it's painful for programmers to deal with two interface styles for operations on the type. IDEs tend to provide better productivity-enhancing completion features for member functions. C++03 doesn't offer any good solutions to this pain as composition is also painful.
-
Billy ONeal about 13 years@Tony: I strongly disagree on both fronts. C++ is not designed to be a syntacticly pretty language. If one cannot deal with nonmember functions then one shouldn't be using C++.
-
Billy ONeal about 13 years@Tony: For an example, I don't think most C++ programmers have problems using the STL -- but the STL is full of free functions which operate on types. Want to sort a
vector
? You're going to be calling the free functionstd::sort
. -
Tony Delroy about 13 years@Billy: consistency and tooling productivity dismissed with "not designed to be pretty"? Is there a connection? ;-P. It's not what one can deal with, but making it simple, elegant and intuitive to do what's often wanted and useful. C++ beats every alternative I know but can improve. C++0x will - but a lot of the proposals incorporated therein were initially resisted with the same kind of passion to protect the status quo that you display above. And your example falls afoul of "Learnt how to sort a
vector
, now tryingstd::sort(my_list.
...)
" ;-) -
Matthieu M. about 13 years@Tony: Right, I keep forgetting that not everyone will remember than when you can choose between derivation and composition, you should pick composition because it's a weaker relation-ship. I'll expand the answer.
-
Billy ONeal about 13 years@Tony: My point is that I disagree that productivity is affected. If one's productivity is that affected by how "pretty" it is, then one should change language. Consistency, on the other hand, is entirely about "pretty" here. The syntax -- what makes it pretty or not -- is inconsistent but what is actually being done is not -- a function is being called in both cases. As for
std::list
-- there are good reasons forsort
to be a member function onlist
-- namely, one needs access to the internals of the list in order to write an efficient sort for it. -
Billy ONeal about 13 yearsAs for C++0x, I don't believe any of the changes made in that standard change things -- one should still prefer non-friend non-member functions whenever possible.
-
Tony Delroy about 13 years@Billy: inconsistency goes deeper than "pretty". Consistency aids generic specification of algorithms. To pursue your
sort
example, say you're writing a templated function that operates on an arbitrary container and gets to some point where it wants to sort it - you have to use some hackery with template specialisation, policies, traits and/or SFINAE just to invoke the correctsort
function. What seemed a simple step just became a significant distraction from the real work. Separately, relist::sort
- the less edge-cases programmers must know to write good code the better. -
Billy ONeal about 13 years@Tony: Yes, consistency of design is important too, but the consistency issue you point at is merely syntactical.
-
Billy ONeal about 13 years@davka: That's a mixin and therefore should use
private
inheritance. Clients don't need to know that it uses logging -- that should be encapsulated by the logger. Otherwise, I would make the logger a member of the class (using composition) rather than inheritance. -
davka about 13 yearsyou are right, I was not aware of this term. I need to read up mixins. any good refs?
-
Billy ONeal about 13 years@davka: Basically, a mixin is an "implemented-in-terms-of" relationship. Your
UsesLogging
class is not part of the interface ofA
, so therefore it shouldn't bepublic
ly inherited from. Public inheritance is for taking the interface of a base class. If you were to modifyA
's interface usingUsesLogging
, (e.g. by exposing functions ofUsesLogging
inA
) then you would be in a polymorphic situation. Rule of thumb ==public
inheritance means "is-a",private
inheritance means "implemented-in-terms-of". -
Billy ONeal about 13 years@davka: Hmm.. thought about it some more and came up with a non-polymorphic use for
public
inheritance -- for when you're using the CRTP in a base class for template meta programming purposes, e.g.boost::iterator_facade
. -
Tony Delroy over 12 yearsBilly: this answer goes from authoritarian dictates ("idiomatic", "absolutely no reason") to generalising overly from a very specific counter case (client code, but the technique may be under consideration for an area of application code, or internal to a library), "lead to inconsistency" and "cannot simply take [because] different size" / "automatic storage" argument is just wrong - the string base is copied cleanly into the right amount of stack - other data is ignored. Your codepad example operates as expected. Actual problems you don't list: class invariants and unsafe deletion.
-
Billy ONeal over 12 years@Tony: Yes, that "very specific corner case" is called an "example". Those are generally used to demonstrate programming ideas to beginners. I state that the slicing problem causes unexpected client behavior, and then show a case where it causes unexpected behavior. As far as the base being copied cleanly, I believe I said that; it says "using the base class' copy constructor". However, a typical programmer's look at this code would suggest that the derived class was actually copied instead. The size argument is there to show why the derived copy constructor cannot be called instead.
-
Viet almost 12 years+1 Now I know how to prevent usage of new/delete operators on a specific class.
-
DarkWanderer over 9 yearsThere is at least one reason to inherit from std::string: creating classes for entities which are semantically different but are represented by string. Inherited class allows to never accidentally mix one with another - which is an immense help when refactoring. The class definition itself may even be empty, I.e. no problems with destructors
-
Billy ONeal over 9 years@Dark: Actually, that's a terrible reason to inherit from
std::string
, because it allows your extra semantic meaning to be silently stripped off of the string because your new type is always implicitly convertible to string. If you have data with semantic meaning feel free to wrap astd::string
, but don't inherit from it. -
Billy ONeal over 9 years@Dark: Even if the class definition is empty,
delete
ing aYourNewType
through astring*
is always undefined behavior. The optimizer can assume that the pointer does not alias to that type, for example. -
DarkWanderer over 9 years@BillyONeal It's not UB - just string's constructor will be called - which is fine, as the class has no own members and no meaningful destructor. At least, that's what I see in every source. And implicit conversion is actually fine, as long as they can't be implicitly converted one to another (which they can't, as that would require 2 conversions)
-
DarkWanderer over 9 yearsBut let's just say it's an exception to the rule, not a counterexample.
-
Billy ONeal over 9 years@DarkWanderer: Yes, it is UB. See C++11 5.6.3 [expr.delete]/3 : if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.
string
has no virtual destructor, so deleting through astring*
pointing to something else is undefined, whether the derived class has a body or not. -
DarkWanderer over 9 yearsOkay, convinced. Thanks for the insight
-
Cheers and hth. - Alf over 7 years−1 "There is absolutely no reason to publicly derive a class in C++ if you're not trying to do something polymorphic" is very much at odds with reality.
-
Cheers and hth. - Alf over 7 yearsUhm, just one sentence further, "when you derive a class in C++ most users of the base class need to know about that kind of a change", that's so nonsensical that my head is spinning. Whoever upvoted this totally lacked analytical skills.
-
Billy ONeal over 7 years@Cheersandhth.-Alf yeah; I need to fix this to suck less. I still think this advice is true in the majority of circumstances for the majority of programmers; if only as a first approximation to avoid this "derivation just to tack on convenience/utility functions" pattern I see new folks do; but I don't agree with everything I wrote 5 years ago. Stand by.
-
Billy ONeal over 7 years@Cheersandhth.-Alf don't agree on "nonsensical" -- the slicing problem is a thing. You don't get implicit pass by pointer as happens in other languages that allows completely transparent derivation.