18

make_shared is more performant than separately calling new and creating a shared_ptr because make_shared allocates space for the reference count and weak count in the same memory block as the client object instance (effectively giving the shared_ptr most of the performance benefits of an intrusive_ptr).

enable_shared_from_this gives a shared pointer without having a reference to any shared pointer. Therefore things like the reference and weak count have to be somehow accessible from inside the client object. Therefore, it would be sensible for enable_shared_from_this to cause an intrusive count similar to make_shared.

However, I have no idea how something like that might be implemented (and I'm not sure I'd follow what was going on in there even if I look at the actual source).

Would it make sense then (for performance reasons) to tag my class with enable_shared_from_this if I know it's only ever going to be used as a shared_ptr and never as a raw object?

Xeo
  • 129,499
  • 52
  • 291
  • 397
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • 2
    But if you know it's gonna be used only for `shared_ptr`, why not make it suitable for an `intrusive_ptr` instread? – Armen Tsirunyan Jun 28 '11 at 22:09
  • @Armen: Not if I'm using `std::shared_ptr` (There's no `std::intrusive_ptr` is there? I'm not sure...). Of course if I'm already using Boost I'd use `intrusive_ptr`. Also I don't think `intrusive_ptr` has a `weak_` variant. – Billy ONeal Jun 28 '11 at 22:09
  • Is it acceptable if the optimisation is only effective when `weak_ptr` is not used? Does `use_count()` have to be exact? – curiousguy Oct 10 '11 at 17:21
  • @curiousguy: I believe the answer to this was "no, there is no performance benefit" So, no, the optimization is not effective there, because it is not effective at all. – Billy ONeal Oct 10 '11 at 20:32
  • If we can assume no overloaded operator delete, no Deleter, and no `weak_ptr`, then it's possible to optimise the memory allocation. – curiousguy Oct 10 '11 at 23:08

3 Answers3

8

I have never dug into the details of implementation, but for shared_from_this to work, the object must already be managed by an external shared_ptr, so it is to some extent unrelated. I.e. the first shared_ptr might have been created with make_shared in which case the count and object are together (as you say intrusive pointer like), but that does not need to be the case.

My first guess is that enable_shared_from_this adds the equivalent of a weak_ptr, rather than a shared_ptr. EDIT: I have just verified the implementation in gcc4.6:

template <typename _Tp>
class enable_shared_from_this {
...
mutable weak_ptr<_Tp> _M_weak_this;
};
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • I'm checking this because I think all these answers are (mostly) equally good, but you've taken the time to answer comments and such in the other answers. – Billy ONeal Jun 28 '11 at 23:20
  • @Billy, I do it because I enjoy it, I am actually learning the gory details of how this is implemented. I had an idea of what it represented, but the question and the comments lead to digging into more and more detail and that in turn leads to learning, which is a reward in itself. As you say, all the answers are valid, and you can use any criteria you want to provide the extra rep. bdonlan had the first answer, so that might be a reason to accept his, and Dennis has the lesser reputation, and it can encourage newer users to participate... – David Rodríguez - dribeas Jun 28 '11 at 23:37
1

I don't believe so. How enable_shared_from_this is implemented isn't strictly defined, but an example implementation is present in the standard that corresponds to how boost does it. Basicaly, there is a hidden weak_ptr that shared_ptr and friends has access to... anytime a shared_ptr is given ownership of an object derived from enable_shared_from_this, it updates that internal pointer. Then shared_from_this() simply returns a strong version of that weak pointer.

In the general case, the implementation can't really assume that nobody will ever go shared_ptr(new T) instead of using make_shared, so an intrusive reference count would be risky. You can instead make that guarentee yourself, by whatever means you use to construct the objects in the first place.

Dennis Zickefoose
  • 10,791
  • 3
  • 29
  • 38
  • 1
    @Billy: Risky: you create the object (with the embedded count), you bind it to one `shared_ptr`, then you extract a few `weak_ptr`, you release the `shared_ptr`. The effect of that is that the object is *destroyed* (the *deleter* is called), yet the reference count (weak reference count) is still required to free the count object. If the count was embedded inside the object, then those `weak_ptr` would be referring to a *destroyed* object, and access to the count would be UB. Oh, well... it is not risky, just undefined behavior. – David Rodríguez - dribeas Jun 28 '11 at 22:52
  • That's not what I was thinking of, but it works even better. I was more considering the abiguity about having a statement `T* t = new T` being a pointer to an object with a reference count of 0. If the reference count is zero, it is supposed to have already been destroyed, but it is still a perfectly valid object. In other words, I was right about it being risky, just for the wrong reasons :-) – Dennis Zickefoose Jun 28 '11 at 23:00
  • There is also the consideration that a completely different smart pointer might be used. An object derived from `enable_shared_from_this` can be thrown into a `unique_ptr`, for instance; you just can't use `shared_from_this` and expect a valid result. But if the reference count were part of `enable_shared_from_this`, if you called `shared_from_this` you would get a valid `shared_ptr`, and you'd end up with two smart pointers both thinking they own a single object. – Dennis Zickefoose Jun 28 '11 at 23:04
  • "it is supposed to have already been destroyed" <-- So? It would be quite simple for `shared_ptr` to handle that in it's constructor. E.g. "If the reference count is zero, then we know no shared_ptr already exists for this..." – Billy ONeal Jun 28 '11 at 23:05
  • @Dennis: You have that same problem just creating a `shared_ptr` ever. Storing the reference counts inside the client would not change that. – Billy ONeal Jun 28 '11 at 23:06
  • 2
    @Billy: Currently if you have a `weak_ptr` and no `shared_ptr` the strong reference cound hits 0, and the object is deleted, but the count object exists (as long as there is at least one `weak_ptr`). Now, that state of the count can represent to different outcomes: the object has not been previously managed, or else the object has already been deleted. --and this is ignoring the whole UB business. – David Rodríguez - dribeas Jun 28 '11 at 23:16
  • @David: That's not really an issue because you can't get a `weak_ptr` without making a `shared_ptr` first. The state of "ref count is zero means deleted" only really matters for `weak_ptr`s. – Billy ONeal Jun 28 '11 at 23:19
  • @DavidRodríguez-dribeas "_those weak_ptr would be referring to a destroyed object_" a destroyed atomic count looks very much like a non-destroyed atomic count... it's just an integer. – curiousguy Oct 09 '11 at 00:36
  • @curiousguy: With the small difference that accessing that *atomic_int* is Undefined Behavior because it was already destroyed... I have the feeling that this would better be discussed on a chat... – David Rodríguez - dribeas Oct 10 '11 at 08:06
  • @DavidRodríguez-dribeas Boost: That atomic_int is actually just a `long`. A "destroyed" `long` is still the same `long` it was before destruction. – curiousguy Oct 10 '11 at 12:59
  • @curiousguy: You do realize what Undefined Behavior means, don't you? It does not mean that it will fail, it just means that the behavior is undefined and the result is free to change from *seems to work*, to *crashes* or *produces unexpected results*. The standard is clear that you cannot access an object that is destroyed, regardless of the type. I really don't understand where you are trying to get with the whole it is UB, but it works for me. You might have code that is ported to a platform with no atomic int operations where the atomic int contains a mutex, then you are done. – David Rodríguez - dribeas Oct 10 '11 at 14:47
  • @DavidRodríguez-dribeas "_You might have code that is ported to a platform with no atomic int operations where the atomic int contains a mutex, then you are done._" I have no idea what you are talking about. **I am talking about an hypothetical optimised `boost::shared_ptr` (or `curious_guy::shared_ptr`) implementation.** Obviously, if a mutex is involved, the implementation would have to make sure it doesn't go away. With pthread this is trivial. – curiousguy Oct 10 '11 at 14:56
  • @curiousguy This is fairly simple, if you think you can provide an implementation that will not trigger Undefined Behavior according to the standard, just do it, offer it as an answer and we will all learn from it. – David Rodríguez - dribeas Oct 10 '11 at 15:51
  • @DavidRodríguez-dribeas Actually, I just realized that I don't know how to handle member delete operator + virtual dtor + derivation. – curiousguy Oct 10 '11 at 16:55
  • @curiousguy: `enable_shared_from_this` uses the CRTP so you don't need a virtual destructor (you can cast `this` to the template argument type to obtain a pointer to the actual type of the object). I don't see why you would need to provide a member `delete` operator, but just assume that we both knew how it is done, how would you provide a `enable_shared_from_this` that does not cause Undefined Behavior and that holds the reference count together with the object? – David Rodríguez - dribeas Oct 10 '11 at 17:12
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/4149/discussion-between-david-rodriguez-dribeas-and-curiousguy) – David Rodríguez - dribeas Oct 10 '11 at 17:13
0

Boost's enable_shared_from_this does not change the implementation of shared_ptr itself. Remember that shared_ptr is paired with weak_ptr - this means that, even after the object is deleted, the tracking data may need to remain around to tell the weak_ptr that the object is dead. As such, it can't be embedded into the object, even with enable_shared_from_this. All enable_shared_from_this does is embed a pointer to said tracking data into the object, so a shared_ptr can be constructed with just a pointer to the object.

This is also why intrusive_ptr cannot have a weak_intrusive_ptr variant.

bdonlan
  • 224,562
  • 31
  • 268
  • 324
  • Erm, of course it can be embedded in the object. `make_shared` already does it! – Billy ONeal Jun 28 '11 at 22:36
  • 1
    @Billy ONeal: That's not embedded into the object- that's just allocated in the same space. There's a difference. – Puppy Jun 28 '11 at 22:38
  • @Billy: While you can imagine an scenario where by using `enable_shared_from_this` in an object the reference count is stored together with the object (in the same way that `make_shared` does), that is not the case, and would require some metaprogramming so that when you use `shared_ptr p( new Type );` the compiler realizes that `Type` contains it's own reference count, and to use that count. At the same time, the implemenation of `shared_ptr` would have to be able to acquire ownership of an object for which the count is 0, but is still alive which currently is impossible... – David Rodríguez - dribeas Jun 28 '11 at 22:44
  • 1
    @David: I think that would be possible. `shared_ptr` already has to change it's implementation to update state inside the client object when `enable_shared_from_this` is specified. – Billy ONeal Jun 28 '11 at 22:48
  • @Billy: we are keeping some sort of strange conversation in other people's answers. Read my comment to this other [answer](http://stackoverflow.com/questions/6513660/can-tagging-a-class-with-enable-shared-from-this-increase-performance/6513934#6513934). The count cannot be an internal part of the object as that could cause undefined behavior (if when the last `shared_ptr` is destroyed there are still any `weak_ptr`s). I recommend that you take a look at the implementation (in boost, it is simpler to follow than in c++0x) of `make_shared` – David Rodríguez - dribeas Jun 28 '11 at 23:00
  • @David: I agree on the "well the weak block causes the destruction problem", but even if it does create the `weak_ptr` inside the client, the implementation must change at least slightly so that the `weak_ptr` gets updated when the `shared_ptr` is constructed. – Billy ONeal Jun 28 '11 at 23:03
  • The implementation detects that sharing from this is enabled, and that is used externally: the constructor of `shared_ptr` will update the `weak_ptr` through a member function (in boost 1.42, there is a *public* (documented as private) member method that updates the weak pointer when the object is first handed to a `shared_ptr`. – David Rodríguez - dribeas Jun 28 '11 at 23:11
  • 1
    @David: Which means that shared_ptr's constructor needs to change it's behavior if `enable_shared_from_this` is there, to call that member to update the weak_ptr. – Billy ONeal Jun 28 '11 at 23:24
  • @Billy: The implementation of `shared_ptr` constructor is exactly the same (as example, one of the constructors): `template shared_ptr( Y* p, D d ) : px(p), pn(p,d) { detail::sp_enable_shared_from_this( this, p, p ); }` where `px` is the pointer, `pn` is the pointer to the reference count and the call in the constructor block is a call to a template, that is overloaded to accept `enable_shared_from_this` objects in one of the variants, and in that particular case, it updates the `weak_ptr` stored *inside* the object. – David Rodríguez - dribeas Jun 28 '11 at 23:33
  • 1
    @David: I would call that "changing the implementation". It is changing it's behavior based on whether or not enable_shared_from_this is tagged on the variable. – Billy ONeal Jun 29 '11 at 00:06
  • It's true that one can, in theory, alter the `shared_ptr` refcounting implementation based on whether its template argument descends from `enable_shared_from_this`. However most implementations do not do this, in part because it leads to some really tricky race conditions when a `weak_ptr` is dereferenced at the same time as the last `shared_ptr` reference is removed. – bdonlan Jun 29 '11 at 00:37
  • "_race conditions when a weak_ptr is dereferenced at the same time as the last shared_ptr reference is removed_" which race conditions? can you explain? – curiousguy Oct 09 '11 at 00:43
  • @curiousguy, if `enable_shared_from_this` embeds a refcount in the object itself, this means that you have to somehow update all `weak_ptr`s from pointing to the object to some NULL state when the last reference is removed - while those `weak_ptr`s may be used by other threads at the same time. If the refcount is external, you can simply leave the refcount around until all weak_ptrs are destroyed. – bdonlan Oct 09 '11 at 00:58
  • "_you have to somehow update all weak_ptrs_" You don't. "_when the last reference is removed_" you only have to destroy the managed object, and keep the memory. A "zombie" weak pointer (one that cannot be converted into a strong pointer) only needs `weak_count_` and `use_count_` in `boost::detail::sp_counted_base`, that is too `long` members. These two `long` will still be there after the object has been destroyed. – curiousguy Oct 09 '11 at 01:15
  • @curiousguy, now you need to require a custom `operator new` for `enable_shared_from_this` classes, so that you can delete it safely in a step separate from destroying it :) – bdonlan Oct 09 '11 at 01:33
  • @curiousguy, also note that the shared_ptr constructor takes a functor object that destructs _and_ deletes the object in question, so this optimization would require a change to the public API – bdonlan Oct 09 '11 at 01:45
  • "_you need to require a custom operator new_" I don't get what you mean. "_also note that the shared_ptr constructor takes a functor object that destructs and deletes the object in question_" Then this particular case wouldn't be optimised. – curiousguy Oct 09 '11 at 02:04
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/4114/discussion-between-bdonlan-and-curiousguy) – bdonlan Oct 09 '11 at 02:13