12

What is so bad about doing something like this?

class myclass : public std::shared_ptr<myotherclass> {
     // some code,  an allocation is never done
     std::string get_info () {
          if(*this != nullptr) return "<info>" + (this->info * 3) + "</info>";
          else return "";
     }
 };

when no allocation is done in the class --- it is just to provide some decoration like above?

ribamar
  • 1,435
  • 1
  • 16
  • 26
  • 2
    What is so good about doing something like this ? – Drax May 18 '15 at 17:14
  • In most cases you should be able to describe the inheritance relationship with the English phase "is a". So in the above case what you are saying is: myclass "is a" std::shared_ptr. This is probably not what you mean. – Richard Critten May 18 '15 at 17:19
  • It's not "bad" so much as "weird". Surely that should be a member of `myotherclass` itself (or perhaps a non-member acting on `myotherclass`), not something bolted on to some specific smart pointer type? – Mike Seymour May 18 '15 at 17:37
  • If the pointer is `nullptr`, your `return 0;` is undefined behaviour, since you can't construct `std::string` with null pointer, see §21.4.2, 8 "Requires: `s` points to an array of at least `traits::length(s) + 1` elements of `charT`." – milleniumbug May 18 '15 at 18:21
  • @milleniumbug edited to avoid this kind of confusion unrelated to the question. thanks for pointing out. – ribamar May 20 '15 at 07:32
  • @drax http://en.wikipedia.org/wiki/Polymorphism_%28computer_science%29 – ribamar May 20 '15 at 07:34
  • It says `an allocation is never done` but then it referes to a new member named `info`. It looks like `info` must have been allocated as a member. What are you really doing? – Potatoswatter May 20 '15 at 07:35
  • @ribamar Sorry but there is no polymorphism in your example. Or do you mean to use a pointer on `std::shared_ptr` to store your `myclass` ? In that case as the accepted answer says it, don't do that :) – Drax May 20 '15 at 08:23
  • @drax: no, i didn't mean that. Can't you see the polymorphism on it? Consider `class Apple: public Fruit, public Food {... }`. Full polymorphism means that anytime you pass `Fruit` or `Food` you can provide `Apple`. In the same way, I meant a class that is polymorphic: something else (e.g, serializable, as I could use with `<<` to `cout`) and a `shared_ptr` at the same time. (And my question, already answered, wasn't if I shouldn't do that, it was why I shouldn't). – ribamar Nov 27 '15 at 14:37

3 Answers3

14

In principle it is allowed to derive from STL classes, see here and here. However, you have to be aware that you should not work with a pointer to the base class -- i.e. a std::shared_ptr<myotherclass>* in this case.

So this and variants thereof should be forbidden:

std::shared_ptr<myotherclass>* ptr = new myclass(/* ... */);

... but agreed, that looks a bit synthetic.

Why is it forbidden? Because the STL classes do not have a virtual destructor. So when you want to delete your allocated class, the derived part remains. This in turn invokes undefined behaviour and possibly creates a memory leak -- even if you do not have some allocations in your derived class.

In order to do so, one possibility is to derive privately from shared_ptr:

class myclass : private std::shared_ptr<myotherclass> {};
                ^^^^^^^

This however might bring in problems with the binary compatibility, see the comments to this answer.

On the hand, even though the former is allowed, you can go on less error-prone and either use composition, where you make the shared_ptr a member of myclass and expose the required functionality (with the drawback that you sometimes have to expose a lot). Or you can set up a standalone function which does what you want ... I know you knew that ;-)

Community
  • 1
  • 1
davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • 1
    `std::shared_ptr* ptr = …` is nonsense. If a virtual destructor becomes necessary for an RAII class, something has already been badly abused. – Potatoswatter May 20 '15 at 07:33
  • @davidhigh - very complete answer: shows the limits of usability ("you should not work with a pointer to the base class"), points to references (in one of them I could see exactly where on the specification i was making mistake), and, demonstrates comprehension of that what the question maker was trying to achieve ("expose the required functionality (with the drawback that you sometimes have to expose a lot). Or you can set up a standalone function"). – ribamar May 20 '15 at 07:42
  • @Potatoswatter: `std::shared_ptr* ptr = …` *is* nonsense, of course ... and in fact so absurd hopefully nobody will ever do it by mistake. – davidhigh May 20 '15 at 08:24
4

Since you will never manually delete it (and you should never manually delete anything, which is rather the point of shared_ptr in the first place), virtual destructors aren't really an issue.

Some interoperability problems may come up, though.

  1. You only get your derived class when you create specific instances of it. When you get a shared_ptr from somewhere like get_shared_from_this, it won't include your info.

  2. Function templates overloaded on shared_ptr<T> won't see the inheritance. Your derived class will suddenly appear foreign to random functions such as std::static_pointer_cast.

Fortunately, the C++ standard library is full of neat extensibility hooks. You can install a custom deleter like so:

template< typename t >
struct my_deleter
    : std::default_delete< t > {
    std::string info;

    my_deleter( std::string in_info )
        : info( std::move( in_info ) ) {}
};

std::shared_pointer< foo > myfoo( new foo, my_deleter{ "it's a foo" } );

and retrieve the info with a non-member function:

template< typename t >
std::string get_my_info( std::shared_ptr< t > ptr ) {
    my_deleter< t > * dp = std::get_deleter< my_deleter< t > >( ptr );
    if ( ! dp ) return {};
    return dp->info;
}

This isn't a very good program architecture, since there's only one custom deleter slot per shared object. It can do in a pinch, though.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
3

I would suggest using std::enable_shared_from_this<> and then this->shared_from_this().

zmag
  • 7,825
  • 12
  • 32
  • 42
Doug Royer
  • 67
  • 5