4

I need to have a shared counter in my class (to call some function when counter goes to zero). I can use a shared_ptr<char> with a deleter for that, but this approach has an overhead of allocating an unneeded char and keeping a pointer to it.

Basically, I need reference counting part of shared_ptr. I do not see how I can utilize shared_ptr and avoid this overhead.

Is there a portable C++11 implementation (i.e., using standard c++11 and std only, no explicit mutexes, etc.) of shared counters?

PS. Counter is not unique to the entire class. I may have objects a1, a2, a3 of my class that share same counter. And b1, b2, b3 that share different counter. So when last one of a1, a2, a3 goes out of scope something (related to a1, a2, a3) should happen. When last one of b1, b2, b3 goes out of scope, something (related to b1, b2, b3) should happen.

Thanks

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
user2052436
  • 4,321
  • 1
  • 25
  • 46

3 Answers3

6

A simple atomic<int> should suffice. I don't see any need for anything more complex.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • I like the way you think! :P – ScarletAmaranth Jan 07 '14 at 15:13
  • `atomic*` where the last one to decrement deletes it? – Yakk - Adam Nevraumont Jan 07 '14 at 15:15
  • No, that's not safe. Just a plain `atomic`. From the way I understand the OP's question, his class is already shared, he just needs a counter inside. – Sebastian Redl Jan 07 '14 at 15:17
  • Counter is not unique to the entire class. I may have objects a1, a2, a3 of my class that share same counter. And b1, b2, b3 that share different counter. So when last one of a1, a2, a3 goes out of scope something (related to a1, a2, a3) should happen. When last one of b1, b2, b3 goes out of scope, something (related to b1, b2, b3) should happen. – user2052436 Jan 07 '14 at 15:29
3
std::shared_ptr<void> p(nullptr, MyDeleter());

This does precisely what you want.

Live example

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Thanks. This sounds good enough. Though, there is memory overhead because `shared_ptr` will have `void*` pointer inside, right? – user2052436 Jan 07 '14 at 15:24
  • @user2052436 From [shared_ptr - Implementation notes](http://en.cppreference.com/w/cpp/memory/shared_ptr#Implementation_notes) a typical implementation holds only two pointers ... – Olaf Dietsche Jan 07 '14 at 15:29
  • See http://ideone.com/g7idRY for a better example. Your example calls MyDeleter before 5 in any case, whether you have the `y` pointer or not. – Olaf Dietsche Jan 07 '14 at 15:36
  • @user2052436 Yes, it will have such "overhead." Are you so memory-constrained that an extra pointer in the footprint of your class matters. – Angew is no longer proud of SO Jan 07 '14 at 15:43
  • @Angew No, your solution is what I need. – user2052436 Jan 07 '14 at 15:59
  • @user2052436 As noted in http://stackoverflow.com/questions/20985667/why-doesnt-stdshared-ptr-need-to-know-complete-type-if-its-constructed-from/20986004#20986004 the `nullptr` `std::shared_ptr` maybe should be treated as *empty*, and empty `std::shared_ptr` should not have its deleter called. One will have to double check the standard, but "If `*this` owns an object and it is the last shared_ptr owning it, the object is destroyed through the owned deleter. *Otherwise does nothing*." is the documentation over at cppreference. The danger is that future revisions will silently break your code. – Yakk - Adam Nevraumont Jan 08 '14 at 14:18
  • @Yakk Interesting. I believe I didn't find this wording in the standard, but I'll have to double check (my copy is not on hand at the moment). Nevertheless, it should definitely work with passing a dummy address instead of `nullptr`. – Angew is no longer proud of SO Jan 08 '14 at 20:34
1

Try std::shared_ptr<void> ptr = std::make_shared<char>();. This does have a single byte overhead (which probably rounds up for alignment reasons), but the char allocated is in the same block as the reference counting implementation when you use make_shared to create your shared_ptr.

Another approach would be to use an "at exit scope" object:

struct at_exit_scope {
  std::function<void()> f;
  ~at_exit_scope() { f(); }
  template<typename F>
  at_exit_scope( F&& f_ ):f(std::forward<F>(f_)) {}
  at_exit_scope() = delete;
  at_exit_scope(at_exit_scope const&) = delete;
};

then do a shared_ptr<at_exit_scope> ptr = std::make_shared<at_exit_scope>( [=]{ /* code */ } ). This eliminates the need for a deleter, and replaces it with a std::function's overhead.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524