10

I have a class which I know will always be owned by a std::shared_ptr. However passing shared_ptr or even weak_ptr to functions and methods that don't need ownership or lifetime guarantees creates unnecessary overhead. To get around this I often pass raw pointers to functions. The class itself inherits from std::enable_shared_from_this so if the function needs to take ownership of the pointer it can use a method of the class to get a shared_ptr.

This is all working beautifully. However there are occasions where I don't really want to make a shared_ptr from a raw pointer, what I want instead is a weak_ptr.

From what I understand of the usual implementation of std::shared_ptr it has two atomic variables used as reference counters; one for shared_ptr, one for weak_ptr.

If all I have is a raw pointer to my class and I want a weak_ptr, I must first create a shared_ptr and convert it. Doing so means the reference counters are altered like this:

  • Construct shared_ptr, increment shared_ptr counter
  • Copy construct weak_ptr, increment weak_ptr counter
  • Allow shared_ptr to go out of scope, decrement shared_ptr counter

This seems to go against the idea that "you don't pay for what you don't use". Is there a way for my class to just provide weak_ptr without first creating a shared_ptr?

Fibbs
  • 1,350
  • 1
  • 13
  • 23
  • It's more like you need an equivalent of `shared_from_this` than of `enable_shared_from_this`. Something like `weak_from_this`. – Kerrek SB Jul 09 '16 at 17:16
  • 1
    You are correct, I'll edit the title. There is a `weak_from_this` proposed for C++17 but I'm looking for something I can use now. – Fibbs Jul 09 '16 at 17:18
  • It means that the class is only ever created using `std::make_shared()`. They will always outlast the scope into which I pass a raw pointer. If the raw pointer needs to be stored beyond the scope it is passed to, it is converted back to a `shared_ptr`. – Fibbs Jul 09 '16 at 17:34
  • @Fibbles: "*This seems to go against the idea that "you don't pay for what you don't use".*" In the most technical sense, yes. But despite that aphorism, the C++ standard has *numerous* places where you pay for things you don't use. And I'm not just talking about iostreams. In this case, the cost is so minimal (3 atomic counter changes) that it's probably not worth worrying about. – Nicol Bolas Jul 09 '16 at 22:46
  • @Fibbles: In fact, `shared_ptr` itself is such an object. It has a distinction between the object `get` returns and the object who's lifetime it manages. [Many people never actually use that functionality,](http://stackoverflow.com/q/37377588/734069) but they pay for it, since that's why `shared_ptr` implementations are the size of two pointers. – Nicol Bolas Jul 09 '16 at 22:52
  • `there are occasions where I don't really want to make a shared_ptr from a raw pointer` in the context given makes it sound like you're talking about raw pointers obtained from a `shared_ptr::get`, hopefully not true? – kfsone Jul 09 '16 at 23:32
  • @kfsone Yes they are obtained from `shared_ptr::get`. What's the problem here? – Fibbs Jul 09 '16 at 23:41
  • @Nicolas Bolas I'm aware of the overhead of `shared_ptr` that's the whole reason behind the question. The performance cost of extra atomic counter changes does add up if you do enough of them. – Fibbs Jul 09 '16 at 23:44
  • @Fibbles when you give the shared_ptr constructor a raw pointer, it doesn't know anything about the previous shared pointer, it doesn't participate in the previous shared_ptrs management block: http://stackoverflow.com/a/4665291/257645. Demo: http://ideone.com/3NdwPm – kfsone Jul 09 '16 at 23:47
  • @kfsone That's not what I'm doing. I'm using a raw pointer (which i know for sure is valid) to an object to access the `shared_from_this` method. This method returns a `shared_ptr` which has the same control block as the `shared_ptr` which owns the object. http://en.cppreference.com/w/cpp/memory/enable_shared_from_this – Fibbs Jul 09 '16 at 23:50
  • Possible duplicate of [Why does enable\_shared\_from\_this lack direct access to the embedded weak\_ptr?](https://stackoverflow.com/questions/15928924/why-does-enable-shared-from-this-lack-direct-access-to-the-embedded-weak-ptr) – eel76 Mar 28 '18 at 07:51
  • @Fibbles In theory successive redundant atomic operations, like `x++; x--;` can be optimized. In practice, compiler writers are shy about it even in the most simple cases, for no valid reason – curiousguy Feb 15 '19 at 19:18

3 Answers3

12

Proposal P0033 was accepted for C++17 in the October 2015 meeting, which adds weak_from_this to classes deriving from std::enable_shared_from_this.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
10

Is there a way for my class to just provide weak_ptr without first creating a shared_ptr?

Not in C++14; the only operation that enable_shared_from_this supports is creating a shared_ptr. Now, enable_shared_from_this should have sufficient information to construct a weak_ptr directly. But you can't do it from the outside, as the class doesn't expose its implementation details to you.

C++17 has support for fetching a weak_ptr from an enable_shared_from_this class via weak_from_this.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • I'm accepting this because it is the correct answer as far as C++14 is concerned. I had hoped some guru would post an ingenious workaround but that looks unlikely. – Fibbs Jul 09 '16 at 23:16
  • The equivalent function for creating a `weak_ptr` from `enable_shared_from_this` class is the method `weak_from_this()`. – SagunKho Feb 15 '19 at 18:32
  • 1
    @Fibbles You could simply maintain a `weak_ptr` member yourself and drop the `enable_shared_from_this` – curiousguy Feb 15 '19 at 21:45
  • @curiousguy: You could only do that if you're willing to write your own `shared/weak_ptr` types. See, `shared_ptr` has special code in its constructor such that, when you associate an object with the pointer, it detects if that object is derived from `enable_shared_from_this`. And if it is, it can then hand off a pointer to the shared state to that base class. Bypassing `enable_shared_from_this` would require you to either implement your own `shared_ptr` type or to *manually* initialize the base class whenever you associate an object with a `shared_ptr`. – Nicol Bolas Feb 15 '19 at 21:51
  • @NicolBolas You are suggesting that you support the idea of not forcing the use of a factory to create all objects of any type derived from `enable_shared_from_this`; I'm sorry in my mind the notion that a factory would be used was so deeply held that I forgot to mention it. I find the concept of builtin support of `enable_shared_from_this` inside the constructor of `shared_ptr` abhorrent. I means that `enable_shared_from_this` can't even be a private base even when it's "an implementation detail of the class" in the mind of the programmer. – curiousguy Feb 15 '19 at 21:57
  • 1
    @curiousguy: But it's not an "implementation detail"; it is part of your type's *interface*, as evidenced by the fact that it *becomes* part of the interface via the inherited `shared/weak_from_this` functions. And yes, forcing everyone to use factories when they don't actually have to is stupid. – Nicol Bolas Feb 15 '19 at 21:59
  • @NicolBolas "_forcing everyone to use factories for no reason is stupid_" Yes indeed, emphasis on "everyone" (in every case). But derivation from `enable_shared_from_this` **suggests that the object being managed by a `shared_ptr` is an invariant of the class**; in such case, making all ctor private and forcing the use of a factory seems like a like good idea. I'm not suggesting that factories should be forced in programmers when not such invariant is expected. Most classes in C++ can be made automatic, static, members or dynamically created; some should only be created in some special way. – curiousguy Feb 15 '19 at 22:03
  • @NicolBolas "_But it's not an "implementation detail"; it is part of your type's interface_" This is a false opposition; in general, an interface can be made protected or private, for use by derived classes and the members resp. only the members. You can inherit from an "interface class" (even a Java style pure interface) with protected or private inheritance in C++, it's a sound design. You can't use private or protected inheritance with `enable_shared_from_this` only because of the "magic" collusion with `shared_ptr`. I don't like it. – curiousguy Feb 15 '19 at 22:10
3

It's almost so trivial to implement that it's not worth putting in the library...

#include <memory>

template<class T> std::weak_ptr<T> weak_from_this(T*p) {
  return { p->shared_from_this() };
}

struct S : std::enable_shared_from_this<S>
{
  auto foo() {
    return weak_from_this(this);
  }
};


int main()
{
  auto ps = std::make_shared<S>();
  auto wps = ps->foo();
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 1
    Going to show my noobishness here, but is RVO preventing the construction of `shared_ptr` here? Otherwise the atomic reference counters are still incremented. – Fibbs Jul 09 '16 at 17:36
  • I think the OP is aware of the existing conversion; the idea was to avoid the intermediate reference count bump. – Kerrek SB Jul 09 '16 at 17:38
  • @KerrekSB a I see. No there isn't a way to avoid the bumps before c++17 because the standard does not even indicate that such a weak_ptr exists prior to this. – Richard Hodges Jul 09 '16 at 17:46
  • 2
    @RichardHodges: C++17 will not save you either. The return value of `shared_from_this` is a `shared_ptr`. And thus, in order to get the return value, it must undergo implicit conversion, which is legal. But that means that, at one point, both a `shared_ptr` and a `weak_ptr` exist. C++17's guaranteed elision rules only guarantee elision if you're initializing the *same type*. – Nicol Bolas Jul 09 '16 at 22:42
  • 2
    @bolas I was referring to the new weak_from_this member function in c++17. – Richard Hodges Jul 10 '16 at 08:22
  • Doing this is trivial, but involves three additional and unnecessary updates to the counters for the shared pointer - locking the weak_ptr, creating a new weak_ptr, and the temporary shared_ptr going out of scope. Since the internal implementation stores a weak_ptr anyway it would be nice if I could just straight up get a `const weak_ptr&` and call it a day. – Tasgall Jan 29 '18 at 06:01
  • In principle successive redundant atomic operations, with no intervening function calls, can be optimized. In practice... – curiousguy Feb 15 '19 at 22:23