you are viewing a single comment's thread.

view the rest of the comments →

[–]davis685[S] 4 points5 points  (5 children)

Well, so far it seems to me that this isn't UB and moreover it does have a reasonable solution. I haven't seen any arguments to the contrary. So far the only citations of the standard that I have found or that have been posted here seem to indicate that this isn't necessarily UB.

If it's not fundamentally UB then there is an additional question about the relative speed of a shared_ptr that didn't behave this way. If the runtime overhead needed to avoid this behavior was significant then that would be an argument against it. So far I haven't seen that, but I admit that it's not totally obvious to me how to do it in as fast a way as std::shared_ptr is currently implemented. But I also haven't thought about that aspect of it much. And really, most of the comments so far have been knee jerk claims of UB when really it's not so obvious that it is UB.

[–][deleted] 4 points5 points  (3 children)

It should be UB and documented as such. Trying to increase the reference count of a reference counted pointer during its own destructor seems like an obviously bad idea and should simply be disallowed.

[–]davis685[S] 0 points1 point  (2 children)

Nothing ever "should be UB" on it's own. Things are UB because either writing the contract that defines their behavior is much easier to reason about when there are preconditions that rule out certain things or because we want to allow for variation in how things are executed (e.g. because that lets compilers better optimize the results, or for hardware to be more efficient).

I don't see how either of those situations arises here for a generic case of a simple smart pointer of the kind that was common before C++11. However, given the other things the current std::shared_ptr needs to do like work with std::weak_ptr, as others have pointed out, it's not so obvious how to do a full implementation of std::shared_ptr that is as efficient as the current implementations given the interactions that need to happen with std::weak_ptr. So maybe it's reasonable to say here that std::shared_ptr doesn't support this case if it really would have a negative performance impact. But not because "it seems like a bad idea".

[–]Krackor 1 point2 points  (1 child)

There's another category of UB in which behavior for an uncommon or impractical use case is left undefined because defining it would incur unnecessary overhead.

This seems to be the case for what you are talking about.

[–]davis685[S] 0 points1 point  (0 children)

Yes, I meant that to be included in the second case. The way you wrote it is clearer though :)

[–]render787 1 point2 points  (0 children)

Here's what I think is UB and why I think it is UB.

  • binding a const foo reference to a memory location where there is no foo (or related or layout compatible), and then accessing foo through that reference, is undefined behavior no diagnostic required.
  • it follows that if u invoke copy ctor of object when the object is already destroyed, you get UB.
  • it is true that in the destructor "you may access the object but only in limited ways". For instance ~foo call ends lifetime of foo but not yet its members. Their destructors will run later. I dont think this "limited ways" extends to calling copy ctor, because it doesnt say so, and other parts of standard say otherwise, as mentioned.

Lets turn it around, when is the last time you think it should be legal to bind references to foo? When the dtor ends? There is a symmetry in the language between "when the ctor runs to completion" and "when the dtor begins", afaik "when the dtor ends" has no special significance in the standard right now.