2

I want to use boost signals2 with automatic connection management in a multithreaded application. My class inherits from enable_shared_from_this<> and i want to connect a member method from within another member method. The connection might be rebuilt frequently so my code should be as fast as possible (despite from the boost signals2 performance itself):

typedef boost::signals2::signal<void ()> signal_type;

struct Cat : public enable_shared_from_this<Cat>
{
  void meow ();

  void connect (signal_type& s)
  {
    // can't write this
    s.connect (signal_type::slot_type (&Cat::meow, this, _1).track (weak_from_this ()));

    // ok, but slow?! two temporary smart pointers
    weak_ptr<Cat> const myself (shared_from_this ());
    s.connect (signal_type::slot_type (&Cat::meow, this, _1).track (myself));
  }

  // i am missing something like this in the base class
  // protected:
  //   weak_ptr<Cat> const& weak_from_this ();
};

I know that my design goals might be conflicting (automatic connection management and thread safety but also fast code) but anyway:

  1. Why does enable_shared_from_this<> lack direct access to the embedded weak_ptr<>? I can't see an opposing reason. Is there no use case similar to mine?

  2. Is there a faster workaround than the one above?

Edit:

I know i can do somethink like this, but i want to avoid the additional storage/init-check penalty:

template <typename T>
struct enable_weak_from_this : public enable_shared_from_this<T>
{
protected:
  weak_ptr<T> /* const& */ weak_from_this ()
  {
    if (mWeakFromThis.expired ())
    {
      mWeakFromThis = this->shared_from_this ();
    }

    return mWeakFromThis;
  }

private:
  weak_ptr<T> mWeakFromThis;
};
eel76
  • 97
  • 12
  • Who says that's "slow"? What is "slow" about it? – Nicol Bolas Apr 10 '13 at 15:11
  • I need two temporary smart pointer instances which will result in at least 4 superfluous interlocked operations only to construct a weak_ptr which already exists. – eel76 Apr 10 '13 at 15:14
  • First, it *doesn't* "already exist"; see my answer. Second, that doesn't make it *slow*. Do you have some profiling data that suggests that this is a problem? – Nicol Bolas Apr 10 '13 at 15:16
  • Since `enable_shared_from_this` interface is not _required_ to use a `weak_ptr`, but actually does - why not just make your own `enable_weak_from_this`? The original is < 80 lines including copyright info and whitespace. You want to do something different than the common case provided for by the library, so you get to do it yourself. – Useless Apr 10 '13 at 15:26
  • @Useless: No, this is no option. The original implementation is not 80 lines. You forgot about the code which binds the weak_ptr to the object and you have no control of that code either. Besides, i always prefer to use a common implementation, i.e. std or boost. – eel76 Apr 10 '13 at 15:35
  • @Useless: Another option is to store an additional weak_ptr in my class. Unfortunately i can't initialize it in the constructor because enable_shared_from_this() is not yet bound to the object so i would have to do a lazy initialization on first usage. In this scenario i have a smaller performance penalty in trade for a storage/redundancy penalty. – eel76 Apr 10 '13 at 15:40
  • 1
    This will be a part of C++17, see: http://en.cppreference.com/w/cpp/memory/enable_shared_from_this/weak_from_this – Hao Xi Nov 23 '16 at 14:03
  • @HaoXi: thanx. i am migrating our code base to C++11/14/17 at the moment, so this will do the trick :) – eel76 Nov 28 '16 at 17:05

2 Answers2

2

The reason you don't have access to the weak_ptr is that enable_shared_from_this doesn't have to use one. Having a weak_ptr is simply one possible implementation of enable_shared_from_this. It is not the only one.

Since enable_shared_from_this is part of the same standard library as shared_ptr, a more efficient implementation could be used than directly storing a weak_ptr. And the committee doesn't want to prevent that optimization.

// ok, but slow?! two temporary smart pointers

That's only one temporary smart pointer. Copy elision/movement should take care of anything but the first object.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • I know that the standard does not define/restrict the actual implementation. However, if the standard provides shared_from_this() it could also provide weak_from_this() without restricting the actual implementation either. An implementation which uses weak_ptr internally can then provide a fast(er) implementation of weak_from_this(). – eel76 Apr 10 '13 at 15:23
  • Does copy elision really work here? We have weak_ptr => shared_ptr => weak_ptr and not weak_ptr => weak_ptr => weak_ptr here. – eel76 Apr 10 '13 at 15:24
  • @user2266052: You said that there were "two temporary smart pointers". WP->SP->WP only uses *one* temporary, the SP (with WP->WP, you still have to copy it). So I assumed that the second was the parameter to `weak_ptr`'s constructor or something. – Nicol Bolas Apr 10 '13 at 22:52
  • @user2266052: "*it could also provide weak_from_this()*" You're right; it could. But... why would they? The time for this process is highly unlikely to be a performance issue for anyone. – Nicol Bolas Apr 10 '13 at 22:55
  • I consider both weak_ptr and shared_ptr a smart pointer. We have two additional smart pointers here (a shared_ptr instance and a weak_ptr instance) and optimization can't eliminate them. I stepped through a (release) build and you can clearly see the four (superfluous) interlocked operations (3 inc/dec and 1 exchange operation for msvc2010 with smart pointers from boost). In a simple single threaded setup, the additional cost can certainly be ignored. Other code (like the signal management stuff in my use case) clearly dominates the overall performance. – eel76 Apr 11 '13 at 07:10
  • I have a setup with 16+ threads (on 16+ physical cores) and contention might become a problem for me. I have to avoid every unnecessary interlocked operation. I already ran into performance problems with simple reference counting algorithms. The same code with twice the number of cores performed four times slower due to high contention! Here performance of the surrounding code didn't matter anymore because the reference counting dominated (slowed down) everything. – eel76 Apr 11 '13 at 07:10
  • @eel76: "*We have two additional smart pointers here (a shared_ptr instance and a weak_ptr instance) and optimization can't eliminate them.*" Copying a `weak_ptr` is still creating a new one. And you're asking for it by value, so you're getting a copy. So there is only one *unnecessary* additional smart pointer. And if reference counting and tracking is a point of concern performance-wise, maybe the problem is that you're copying these smart pointers around too much. – Nicol Bolas Apr 11 '13 at 07:34
  • If i use my workaround code then i will return a `weak_ptr<> const&` of course. `slot.track()` also expects a const reference so i have perfect forwarding here (no weak_ptr is copied at all). A c++ standard solution might return by value (so as not to prevent implementation alternatives) and i would save only one temporary smart pointer, but copy elision might get rid of the other one. I am aware of not copying smart pointers whenever possible (and other performance optimizations as well). Otherwise i wouldn't have posted this question. – eel76 Apr 11 '13 at 08:25
-1

It might be because there are no shared_ptr referencing the Cat instance. weak_ptr requires there to be at least one active shared_ptr.

Try placing a shared_ptr as member variable and assign to it first in the connect method:

typedef boost::signals2::signal<void ()> signal_type;

struct Cat : public enable_shared_from_this<Cat>
{
  void meow ();
  boost::shared_ptr<Cat> test;

  void connect (signal_type& s)
  {
    test = shared_from_this();
    s.connect (signal_type::slot_type (&Cat::meow, this, _1).track (weak_from_this ()));
  }
};

But basically, there can be no weak_ptr if there are no shared_ptr. And if all shared_ptr disapear while the weak_ptr is still in use, then the weak_ptr could point to an non-existant object.

Note: My test should not be used in production code as it will cause the object to never be deallocated.

fredrik
  • 6,483
  • 3
  • 35
  • 45
  • You misunderstood my question. There exists a shared_ptr instance which manages the lifetime of the cat object. I'am not having a problem to make my code work (it does) but to make it fast. – eel76 Apr 10 '13 at 15:11