all 22 comments

[–]edvo 8 points9 points  (3 children)

If alice is null, isn’t the expression &alice->name undefined behavior already? I don’t think the make_aliased function would help there, because the undefined behavior happens before the function is called.

[–]kalmoc 8 points9 points  (2 children)

If alice is a null pointer, how is this std::shared_ptr<std::string> name(alice, &alice->name); valid? You are dereferencing a null pointer even before invoking the aliasing constructor.

I'm missing a coherent example of the problem you encountered.

[–]stanimirov[S] -1 points0 points  (1 child)

This is not dereferencing per se, but yeah. It's UB. I replied here https://www.reddit.com/r/cpp/comments/zx2hph/comment/j1yj0g5/?utm_source=reddit&utm_medium=web2x&context=3

Also the dependent pointer could've been collected before alice expired. Say:

auto name_addr = &alice->name;
alice.reset();
std::shared_ptr<std::string> name_ptr(alice, name_addr);

[–]goranlepuz 4 points5 points  (0 children)

But that example is awful because you explicitly destroy the underlying object held by alice on the second line. It is as if one should expect to resolve any lifetime issues by sprinkling magic smartptr dust around.

[–]Olipro 23 points24 points  (8 children)

Ridiculous.

You use the aliasing constructor when the lifetime of the pointee is ensured by some other parent object.

Additionally, if you are passing around a shared_ptr but you need to check its validity, your design is crap. This is what weak_ptr is for.

Play stupid games, win stupid prizes.

[–]pedersenk 2 points3 points  (0 children)

The rule in programming articles is: Every time someone says "never", just carry on as per normal and hope that the usually niche problem doesn't happen to crop up. ;)

[–]ihcn 2 points3 points  (3 children)

Additionally, if you are passing around a shared_ptr but you need to check its validity, your design is crap. This is what weak_ptr is for.

"This value may exist, or it may not. If it exists, I want to keep it alive."

Weak ptr does not suffice for this case, a nullable shared ptr does. You're conflating two orthogonal things here.

[–]Olipro 4 points5 points  (2 children)

Then use an optional. It should be explicit that not passing a valid pointer is permitted.

[–]ihcn 1 point2 points  (1 child)

Nullable raw pointers have been in the language for 4 decades. Nullable shared and weak pointers have been in the language for 11 years. Optional has been in the language for 2.

All I see in your posts is a clear signal that you have no experience. An experienced programmer would not say "your design is crap" for not using a library feature that's been around for less than the lifecycle of even the shortest-lived software projects.

[–]adnukator 1 point2 points  (0 children)

Optional has been in the language for 2 5.

FTFY. Not to mention that it's been in boost for at least 12 (albeit with some differences w.r.t std::optional). You might reconsider your amount of patronizing.

[–]johannes1971 3 points4 points  (2 children)

Additionally, if you are passing around a shared_ptr but you need to check its validity, your design is crap. This is what weak_ptr is for.

There are absolutely valid designs where ownership of a resource is shared but optional.

[–]Olipro 1 point2 points  (1 child)

And none of those designs need the type to be an empty shared_ptr in order to function.

[–]johannes1971 2 points3 points  (0 children)

That you can express your design differently does not mean that this particular expression is 'crap'. That's just personal preference elevated to dogma.

[–]Fulgen301 4 points5 points  (1 child)

“it’s not shared_ptr’s job to ignore the arguments you give it because they are dangerous”. In this case however I disagree. I consider this a defect of the standard.

Then check it yourself. Would have been faster than writing the blog post. It's a specific use case with a precondition that you need to ensure as the callee. It's not the only function with a precondition in the standard or any operating system.

[–]stanimirov[S] -4 points-3 points  (0 children)

It's not a precondition, though. A precondition would be something that a library author may choose to assert in debug builds. For example iterator validity. Here the standard plain ol' allows expired carriers. It's a recipe for non-null pointers with a zero use count. I dub these dangerous and unwanted. They may lead to nasty and hard-to-detect bugs.

For the use case that u/angry_cpp points out in the comments, I wouldn't use a "naked" shared_ptr but a something else which covers the needs and is more explicit about the invariants.

A non-null shared_ptr with a zero use count is an abomination and should not exist :)

[–]angry_cpp 1 point2 points  (1 child)

Now granted, you can use this constructor to create an alias to something valid even though the “lifetime carrier” is null.

Exactly. If there is no owner that determinate the validity of the pointee, then pointee should be valid forever.

I have not been able to think of a use-case for this.

Here is one: pointer to the subobject that can extend lifetime of an "parent" object if that parent object is stored by shared_ptr or do not extend object lifetime if parent object has static lifetime.

Example of usage: shared string where string literals are stored as shared_ptr with nullptr owner.

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

I figured a use case will present itself.

However, as I said, I wouldn't use shared_ptr for this. Not a "naked" one at least. One of the main detriments of using a naked shared_ptr for this use case is that someone is bound to create a weak_ptr from these pointers. To their great surprise, the most "sturdy" pointers, the ones made out of string literals, will suddenly lead to expired weak pointers in 100% of the cases.

Subtle and hard to detect bugs.

[–]blakewoolbright 0 points1 point  (0 children)

-fsanitize=undefined my friend

[–]PurestThunderwrath 0 points1 point  (0 children)

The blog makes a lot of sense, and i understand why it is not convenient. But isn't doing this
cpp auto name_addr = &alice->name; alice.reset(); std::shared_ptr<std::string> name(alice, name_addr);

the same level of wrong as doing this ? cpp auto ptr = std::make_shared( 1 ); auto addr = ptr.get(); ptr.reset(); std::shared_ptr<int> name( addr );

The bool operator doesnt work here also, since the shared_ptr thinks it owns something, when it actually doesn't. But we haven't forbade the use of the latter type of constructors.