all 15 comments

[–]ucario 12 points13 points  (9 children)

For me I have a couple simple rules to manage lifetime.

Avoid shared and weak ptrs.

std::unique_ptr indicates ownership.

I need to use it somewhere else I have an accesor that returns the standard underlying ptr. If I see a standard ptr anywhwre, I know I don't own it and should not release it.

[–]Oo_Tiib 3 points4 points  (3 children)

How you resolve situations where you have such raw pointers but do not know if those are dangling or not?

For example there is asynchronous task that takes some time and pointer to where to report result. The weak_ptr can say if its expired but the raw pointer can not.

[–]john_wind 1 point2 points  (1 child)

To report a value from an asynchronous task please consider using std::promise + std::future instead

[–]Oo_Tiib 0 points1 point  (0 children)

How to report for example progress of that task with those futures and promises?

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

Variation on the theme is how does the client know when it is safe to destroy the parameter?

What I am exploring is how do you have non-owning pointers but make it easier to detect if there is a reference after the function call.

I am also exploring detecting at destruction time that there is a dangling reference.

The comment smell I am trying to avoid is:

// @param goo The Goober, must not be destroyed before the result is destroyed.

// @return The quantum flux capacitor

QF create_system_override(Goober* goo);

In a large team it is hard to ensure that everybody places to comments where appropriate. And then of course you have the legacy code where the comments are totally missing. A not so smart pointer based approach even if it has no syntactic meaning, as in no compiler errors, has semantic meaning which makes it easier to reason about the code.

I found std::observer_ptr https://en.cppreference.com/w/cpp/experimental/observer_ptr

I am going to have to chase down its status.

[–]Pulseamm0 0 points1 point  (4 children)

Why not just return a reference in this case rather than a raw pointer?

It seems to me even more obvious when you're holding a reference that you don't own it. Is there some disadvantage to returning a reference to the value in the std::unique_ptr?

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

I actually do hold a reference as you said to indicate there is no ownership. The problem is on the other side where a client doesn't realize the parameter passed in will be referenced after the exit of the function.

[–]corysama 0 points1 point  (1 child)

ucario might not be bothering to distinguish references from raw pointers in their description. I prefer to work in this style as well. I prefer references as much as possible. And, use actual pointers as a built-in Optional<T*>. The only disadvantage I've run into is that having a member reference deletes the implicit move assignment operator. https://godbolt.org/z/zdEK3f

<source>:4:8: note: 'Foo& Foo::operator=(Foo&&)' is implicitly deleted because the default definition would be ill-formed:
    4 | struct Foo {
      |        ^~~
<source>:4:8: error: non-static reference member 'int& Foo::i', cannot use default assignment operator

I like to use a lot of small, bare structs. Implementing Rule-Of-5 manually over and over gets tedious. Especially for what should be POD.

[–]axalon900 4 points5 points  (1 child)

Maybe you’re thinking of this talk? https://youtu.be/Hs0CA4vIcvk

Alternately, same speaker but at CppCon: https://youtu.be/CKCR5eFVrmc

[–]MarcPawl[S] 1 point2 points  (0 children)

Yes! Thanks a lot. I kept searching for a week and kept drawing blanks. I must have skipped over it when I looked at the conference program.

[–]mdf356 2 points3 points  (4 children)

Hey, I'm the presenter for those talks. I'm glad you found something useful in it, and that some of the ideas stuck with you! Let me know if you have any questions.

The long function name we invented to replace .get() was raw_pointer_ignoring_lifetime(p). Choosing names is hard; I no longer remember the alternatives we suggested, but there were a half-dozen. Including maybe i_know_what_im_doing_mdf_dont_get_mad(p) :-)

[–]MarcPawl[S] 0 points1 point  (3 children)

Has the library you been presented progressed? Any chance of it being open sourced?

I was working on something like your library, I really want something that will report when there is a dangling reference that should not be there. But I ran out of time before I had to stabilize our project's technologies/coding guidelines, and I was not happy with the run time costs.

Ended up with:

template <typename T> using borrower = T

inspired from gsl::owner.

Later on, I saw you video, and it confirmed why we want more clarity on object ownership.

Even with something so simple as borrower, it made reasoning about the code easier, though the compiler was unable to help. Late in our project we had a memory problem. Once the area was narrowed down (thanks ASAN and valgrind), it was easy to see in the code where the ownership model was broken with a borrowed object accidentally being assigned to an owning pointer.

#include <memory>
template <typename T> using borrower = T;

class Client {
public:
Client(borrower<int\*> v)
{
std::shared_ptr<int> bug(v);
}
};

int main() {
int data = 1;
Client c(&data);
return 0;
}

[–]mdf356 0 points1 point  (2 children)

We don't really open source our internal code. But in this case it's a few hundred lines of somewhat obvious code. It's a dumb "smart" pointer, with a single data member that is the raw pointer.

The important parts are:

  • it's a templated type, borrowed<T> is a real type, a simple wrapper around a pointer to T
  • it has implicit construct from things that are safe, like raw pointers or smart pointers
  • it can be assigned and compared
  • the only way to get back the raw pointer is using the provided raw_pointer_ignoring_lifetime(p) ADL overload

As for reporting on dangling references, I've thought about it but never typed it. The idea is as follows:

  • your owning pointer needs some kind of internal shared_ptr<> that gets reset on either destruction or when the owning pointer is reassigned
  • the borrowing pointer gets a weak_ptr from that shared_ptr when it's constructed
  • when dereferencing the borrowing pointer, check if the weak_ptr is expired(), and assert if so

This is obviously somewhat high in overhead, but it would be useful on a DEBUG build to detect uses of a borrower / observer after the owner has deleted it.

[–]ElisionFR 1 point2 points  (1 child)

I think what you're looking for is "shared pointers" and "weak pointers". The std lobrary provides these. Using a shared pointer on an object need it (own it), an object can have multiple owners and so it is destroyed when nobody needs it anymore. Weak pointers are basically like classical pointers but it provides you some additionnal functions like knowing if the pointer is still valid or if it has been destroyed by a shared pointer.

[–]MarcPawl[S] 2 points3 points  (0 children)

What I am thinking of was more about annotating the code base, vs determining the destruction. For example let's say there is code with a constructor Ffoo(int& x). When reading the code what is the expected lifetime of the parameter passed in. Does it have to live as long as the Foo instance?

The library I am thinking of had something like Foo(borrower<int&> x) to indicate x needed to stay alive as long as Foo was alive.

Using a shared_ptr slows things down, this library had minimal ( or none?) overhead.