top | item 38677172

(no title)

foxhill | 2 years ago

ah, sorry, i didn’t read that correctly.

perhaps for values like this you’re fine. i think my point still stands about the reader of a built-in list/sequence type, surely?

and, not to sound facetious, that’s exactly what optimisers do :)

the c++ type system is more than capable about reasoning about lifetimes, the issue is that, with c++, it’s an optional part of the language. also, the lack of non-destructive moves. but to require both of those things in the language would require, essentially, the borrow checker in rust.

discuss

order

kentonv|2 years ago

Unfortunately the C++ compiler cannot reason about much of anything as soon as you make a virtual function call, or even a call into a separate translation unit (unless maybe you are using LTO but that has its own issues).

E.g. if you do:

    {
      auto foo = std::make_shared<Foo>();
      bar->baz(foo);
    }
The compiler has to know what `baz()` does in order to know whether it can elide heap allocation and refcounting of `foo`. `baz()` could, after all, add a refcount on `foo` and keep it somewhere.

If `baz()` is virtual, or just implemented in a source file that the compiler cannot see at the time of compiling the calling code, then there's no ability to optimize at all. Even if the compiler does know the full implementation of `baz()`, eliding the heap allocation is not going to be easy. Maybe if `baz()` is very simple, it can do it? I actually don't know if the compiler is even capable of this when using shared_ptr.

Of course you can always say "well a sufficiently smart compiler could reason about your whole codebase including every implementation of a virtual call" but we program to the compiler we have, not the one we want. And frankly, if you had a compiler that smart it would be able to detect your use-after-free bugs and warn about them, so you wouldn't need to use shared_ptr everywehre.

foxhill|2 years ago

i am quite familiar with compiler internals :)

of course, across the TU boundary things get difficult, but i don’t think it’s fair to dismiss LTO entirely (although.. i agree with the thesis that it’s not particularly.. good)

similarly, de-virtualisation is an optimisation technique compilers will aggressively use to improve performance, although you’re right that it can’t look through another source file, so it is not without limitations here.

but we’re not being general, we’re being specific; the safety issues that are being discussed are well within the remit of the c++ type system here, and i don’t think we’re doing any favours to anyone by letting this rvalue be accessed in this way. it is certainly not idiomatic to provide library code that can so violently implode with seemingly regular use. i find it difficult to believe that lifetime issues like this are undiagnosable.