add operator-> to std::weak_ptr

176 views
Skip to first unread message

Taylor Holliday

unread,
Jan 15, 2015, 4:51:01 PM1/15/15
to std-pr...@isocpp.org
This would be implemented as follows:

   std::shared_ptr<T> operator->() {
      auto shared = lock();
      if(shared == nullptr) {
          throw std::bad_weak_ptr(); // or some other exception
      }
      return shared;
  }
The lifetime of the returned shared_ptr nicely scopes a member function call.

Thoughts? I assume this has been proposed before.

- Taylor

Nevin Liber

unread,
Jan 15, 2015, 5:02:50 PM1/15/15
to std-pr...@isocpp.org
On 15 January 2015 at 15:51, Taylor Holliday <wthol...@gmail.com> wrote:
This would be implemented as follows:

   std::shared_ptr<T> operator->() {
      auto shared = lock();
      if(shared == nullptr) {
          throw std::bad_weak_ptr(); // or some other exception
      }
      return shared;
  }
The lifetime of the returned shared_ptr nicely scopes a member function call.

Thoughts?

What is the use case (other than "we can")?  It means that the caller isn't participating in ownership yet still expects someone else to be keeping the object alive.  Why wouldn't I just pass a shared_ptr instead?

Note:  if the answer is "to detect programming bugs", such as how some people abuse things like vector::at(), I am firmly against that, as that use case is far better served by asserts rather than pretending it is a recoverable error.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Thiago Macieira

unread,
Jan 15, 2015, 5:27:15 PM1/15/15
to std-pr...@isocpp.org
So you can write:

ptr->foo();

instead of

ptr.lock()->foo();

?

Are the 7 extra characters that much of a burden to write? I'd say that being
explicit that there's more happening than just obtaining a pointer is a good
thing. Source code is read more often than it's written.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Nevin Liber

unread,
Jan 15, 2015, 5:31:29 PM1/15/15
to std-pr...@isocpp.org
On 15 January 2015 at 16:27, Thiago Macieira <thi...@macieira.org> wrote:


So you can write:

        ptr->foo();

instead of

        ptr.lock()->foo();

lock() doesn't throw, so the dereference would invoke UB on an empty shared_ptr.

Sean Middleditch

unread,
Jan 15, 2015, 7:44:24 PM1/15/15
to std-pr...@isocpp.org
The implicit safety you're implying here assumes that the user is aware that -> on a weak_ptr is only usable for expressions that outlive the lifetime of the temporary returned by ->. That's just far too big of a surface area to expect. It would mean very "obvious" code like the following would now compile but be dangerously broken:

  weak_ptr<Thing> ptr = get_thing();
  auto& foo = ptr->some_member;
  use(foo); // foo might be long gone by this point

Without comprehensive lifetime tracking a la Rust, there's not a lot of ways to achieve both convenience and safety for your case, as the language has no way to know that the lifetime of `foo` is dependent on the lifetime of the temporary returned by `ptr->`. While yes, we do have other similar surprises lurking in C++ already, there's no good reason to add yet another hidden trap just to save a few characters of typing.

Taylor Holliday

unread,
Jan 15, 2015, 7:54:00 PM1/15/15
to std-pr...@isocpp.org
Hmm. It seems pretty easy to shoot yourself in the foot that way anyway if you don't know what you're doing:

weak_ptr<Thing> ptr = get_thing();
auto& foo = ptr.lock()->some_member;
use(foo); // foo might be long gone by this point

Taylor Holliday

unread,
Jan 15, 2015, 8:16:35 PM1/15/15
to std-pr...@isocpp.org
On Thursday, January 15, 2015 at 2:02:50 PM UTC-8, Nevin ":-)" Liber wrote:
On 15 January 2015 at 15:51, Taylor Holliday <wthol...@gmail.com> wrote:
This would be implemented as follows:

   std::shared_ptr<T> operator->() {
      auto shared = lock();
      if(shared == nullptr) {
          throw std::bad_weak_ptr(); // or some other exception
      }
      return shared;
  }
The lifetime of the returned shared_ptr nicely scopes a member function call.

Thoughts?

What is the use case (other than "we can")?  It means that the caller isn't participating in ownership yet still expects someone else to be keeping the object alive.  Why wouldn't I just pass a shared_ptr instead?

I was using weak_ptr in a template that expects something to behave like a pointer. Ended up having to add a generic function overloaded on weak_ptr that would lock (I couldn't simply pass in a shared_ptr). Turned out not to be that bad actually.
 

Note:  if the answer is "to detect programming bugs", such as how some people abuse things like vector::at(), I am firmly against that, as that use case is far better served by asserts rather than pretending it is a recoverable error.

Oh man we are in different worlds. I subclassed vector to add range checking to operator[] :-\

But point taken, since shared_ptr::operator-> is unsafe it would be kind of weird if weak_ptr::operator-> threw.

David Krauss

unread,
Jan 15, 2015, 8:37:20 PM1/15/15
to std-pr...@isocpp.org

On 2015–01–16, at 9:16 AM, Taylor Holliday <wthol...@gmail.com> wrote:

I was using weak_ptr in a template that expects something to behave like a pointer. Ended up having to add a generic function overloaded on weak_ptr that would lock (I couldn't simply pass in a shared_ptr). Turned out not to be that bad actually.

weak_ptr isn’t a pointer, it’s a contract for getting a pointer.

Perhaps weak_ptr should be an instance of a general “fancy pointer” concept that transforms into something else when dereferenced. Database queries are another use-case of this. Ordinary pointers and smart pointers should satisfy the concept with a trivial transformation.

David Rodríguez Ibeas

unread,
Jan 16, 2015, 8:11:48 AM1/16/15
to std-pr...@isocpp.org
On Thu, Jan 15, 2015 at 8:37 PM, David Krauss <pot...@gmail.com> wrote:

On 2015–01–16, at 9:16 AM, Taylor Holliday <wthol...@gmail.com> wrote:

I was using weak_ptr in a template that expects something to behave like a pointer. Ended up having to add a generic function overloaded on weak_ptr that would lock (I couldn't simply pass in a shared_ptr). Turned out not to be that bad actually.

weak_ptr isn’t a pointer, it’s a contract for getting a pointer.

Agreed, regardless of what the name might hint. The addition of 'operator->'  would make it less obvious that it is *not* a pointer and lead to a weird interface with 'operator->' but not 'operator*' or a way to get the underlying object.
 
Today, even if the name is misleading, at least it is easy to teach that 'weak_ptr' is not a pointer but a mechanism to [possibly] obtain one.

This goes along with a recent request to provide "symmetry" to weak_ptr, providing an aliasing constructor for 'weak_ptr' and an 'unlock' function to 'shared_ptr'. I don't believe there is value of symmetry between a pointer and a non-pointer type. But that is a different discussion.

    David
Message has been deleted

Tony V E

unread,
Jan 17, 2015, 5:10:10 PM1/17/15
to Andrzej Krzemieński
On Thu, Jan 15, 2015 at 7:54 PM, Taylor Holliday <wthol...@gmail.com> wrote:
Hmm. It seems pretty easy to shoot yourself in the foot that way anyway if you don't know what you're doing:

weak_ptr<Thing> ptr = get_thing();
auto& foo = ptr.lock()->some_member;
use(foo); // foo might be long gone by this point


Yep, so let's not make it worse.

And note an important difference in form:   x->  vs  x()->

Any time you see function()->  you should question the lifetime of the return.
Without the () it is easy to forget that x-> might be a function call, and thus something to question.

Also I'd prefer not to end up with code like:

x = ptr->x;
y = ptr->y;
...

where each -> is a separate call to lock().

Tony

Taylor Holliday

unread,
Jan 17, 2015, 6:08:36 PM1/17/15
to std-pr...@isocpp.org
This paper by Stroustrup would seem to endorse my suggestion, no?

Tony V E

unread,
Jan 17, 2015, 7:16:17 PM1/17/15
to Andrzej Krzemieński
On Sat, Jan 17, 2015 at 6:08 PM, Taylor Holliday <wthol...@gmail.com> wrote:
This paper by Stroustrup would seem to endorse my suggestion, no?




No.

-  if A then B doesn't mean if B then A.
Stroustrup is saying *some* wrapping makes sense (or is common, whether it makes sense or not), not that your wrapping makes sense - ie he doesn't mention weak pointers
- that paper is 15 years old, I'm not sure even Stroustrup agrees with it

IMO, his wrapper might make sense for trace-logging, but I still wouldn't recommend it for mutex locking (even if he mentions that as an example), and I suspect he wouldn't really recommend it for locking either - locking on each access is too inefficient, and you don't get a consistent snap-shot of the object you are accessing (ie between ptr->x and ptr->y accesses, the object could change - is that expected?).  I think the locking example falls into the "common" or "possible" category, not the "recommended" category.

Also, even if Stroustrup thought your suggestion was great (and I honestly doubt he would), I even more highly doubt the rest of the committee (in particular the library sub committee, which I am more familiar with) would agree with it.  (And even though Stroustrup is smart and has lots of say in the committee, if the rest of the committee disagrees, it doesn't happen)

But it does highlight that weak_ptr is somewhat poorly named.  Thanks.  I think naming is important; unfortunately too late for this one :-(

Tony



On Saturday, January 17, 2015 at 2:10:10 PM UTC-8, Tony V E wrote:


On Thu, Jan 15, 2015 at 7:54 PM, Taylor Holliday <wthol...@gmail.com> wrote:
Hmm. It seems pretty easy to shoot yourself in the foot that way anyway if you don't know what you're doing:

weak_ptr<Thing> ptr = get_thing();
auto& foo = ptr.lock()->some_member;
use(foo); // foo might be long gone by this point


Yep, so let's not make it worse.

And note an important difference in form:   x->  vs  x()->

Any time you see function()->  you should question the lifetime of the return.
Without the () it is easy to forget that x-> might be a function call, and thus something to question.

Also I'd prefer not to end up with code like:

x = ptr->x;
y = ptr->y;
...

where each -> is a separate call to lock().

Tony

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Taylor Holliday

unread,
Jan 18, 2015, 3:32:45 PM1/18/15
to std-pr...@isocpp.org


On Saturday, January 17, 2015 at 4:16:17 PM UTC-8, Tony V E wrote:


On Sat, Jan 17, 2015 at 6:08 PM, Taylor Holliday <wthol...@gmail.com> wrote:
This paper by Stroustrup would seem to endorse my suggestion, no?




No.

-  if A then B doesn't mean if B then A.
Stroustrup is saying *some* wrapping makes sense (or is common, whether it makes sense or not), not that your wrapping makes sense - ie he doesn't mention weak pointers
- that paper is 15 years old, I'm not sure even Stroustrup agrees with it

The paper says uses the following as example:

grab-lock do-something release-lock 

and that is what I was proposing. The primary difference is that in this case the lock can fail and throw. You’re right, it doesn’t mention weak pointers. But it does endorse the ideas that it is ok in some cases to have operator-> without operator* and that successive locking isn’t necessarily terrible.

Fair point about the paper being old. It might be completely irrelevant.
 

IMO, his wrapper might make sense for trace-logging, but I still wouldn't recommend it for mutex locking (even if he mentions that as an example), and I suspect he wouldn't really recommend it for locking either - locking on each access is too inefficient, and you don't get a consistent snap-shot of the object you are accessing (ie between ptr->x and ptr->y accesses, the object could change - is that expected?).  I think the locking example falls into the "common" or "possible" category, not the "recommended" category.

The concern about efficiency seems like premature optimization to me.

The other concern regarding changes between accesses seems more legitimate.

What are the approaches taken in other languages? ObjC and Swift treat weak references like how I propose: there is no explicit locking.


But it does highlight that weak_ptr is somewhat poorly named.  Thanks.  I think naming is important; unfortunately too late for this one :-(

Tony


What could it have been called?
Reply all
Reply to author
Forward
0 new messages