int x = 123;
delete &x;
and that would compile. But it's not a very good idea and you should be able to, well, not do that.In modern C++, we avoid allocating and deallocating ourselves, as much as possible. But of course, if you jump to arbitrary code, or overwrite something that's due as input for deallocation with the wrong address, or similar shenanigans, then - it could happen.
The modern, safest, way to use C++, is to use smart pointers rather than raw pointers, which guarantee that nothing gets deleted until there are no more references to it, and that at that point it will get deleted.
Of course raw pointers and new/delete, even malloc/free, all have their uses, and without these low level facilities you wouldn't be able to create better alternatives like smart pointers, but use these at your own peril, and don't blame the language if you mess up, when you could have just done it the safe way!
To be more precise, C++'s smart pointers will ensure something is live while specific kinds of references the smart pointer knows about are around, but they won't (and can't) catch all references. For example, std::unique_ptr ensures that no other std::unique_ptr will own its object and std::shared_ptr will not delete its object while there are other std::shared_ptrs around that point to the same object, but neither can track things like `std::span`/`std::string_view`/other kinds of references into their object.
C++'s non-owning "view" classes are a different matter, and the issue there isn't ownership but lifetime of the view vs the underlying data the view is referencing (which in case of string_view could be literally anywhere - a local array of char, a malloc'd block of memory, etc!!).
I'm not a fan of a lot of the (relatively) more recent additions to C++. I think C++14 was about the peak! Given that C++ is meant to be usable as a systems programming language, not just for applications, and given that many new features being added to C++ are really library additions, not language ones, then it's important for the language to include unsafe lower level facilities than can be used for things like that, but actually encouraging application developers to use classes like this that are error-prone by design seems questionable!
Sure, I was just clarifying that "references" in the bit I quoted covers specific things and not everything with reference-like behavior.
As soon as you start mix 'n' matching smart pointers and raw pointers into what they are pointing to, then all bets are off!
Sure, and if you get into it there are two issues here.
1) Smart pointers are a leaky abstraction that still gives you access to the underlying raw pointer if you want, with which you can shoot yourself in the foot.
2) It's not the smart pointer's fault, but the type you use them to point to might also be a leaky abstraction (e.g. std::string, which again allows access to the underlying raw pointer), or might just be some hot garbage the developer themselves wrote!
Literally all std::unique_ptr does is wrap the pointer. If we make two unique_ptrs which both think they own object X then not only will they both exist and point at X (so they don't actually ensure they're unique) they'll also both try to destroy X when they disenage e.g. at the end of a scope, leading to a double free or similar.
Uniqueness is a property you are promising, not one they're granting.
I expect you knew this, but it's worth underscoring that there is no magic here. This really is doing only the very simplest thing you could have imagined, it's a pointer but as an object, a "smart pointer" mostly in the same sense that if I add a cheap WiFi chip to this refrigerator and double the price now it's a "smart fridge".
The smartness, limited though it may be, is just that since it "knows" it's the owner, it knows that it's responsible for releasing it at the appropriate time (reference counted in the case of a shared pointer).
A lot of the value isn't just the automatic release, but that these classes, unique_ptr, and shared_ptr, are built to enforce their semantics, such as literally not being able to copy a unique_ptr, but are able to move it, and copying a shared_ptr being allowed and increasing the reference count of the object.
Yes - they are just simple classes, but using them as intended does avoid a lot of common programmer memory management mistakes, and highly adds to the readability of your program, and ability to reason about ownership, since use of these types rather than raw pointers in effect documents who the owner(s) are, and enforces that this documentation is the reality.
Just because you build a unique_ptr from a raw pointer it doesn't mean that it is now magically owned by the object. You also have to ensure that no other place already did that, which unique_ptr simply cannot enforce.
Python 2 to 3 is a good example of what can be expected to happen - very slow adoption of the new version since companies may just not have the resources or desire to rewrite existing code that is running without problems.
Epochs was given the usual WG21 treatment and that's the end of that. Rust shipped 2021 Edition, and 2024 Edition, and I see no reason to think 2027 Edition won't happen.
The current iteration of Bjarne's "Profiles" idea is in a similar ballpark though it got there via a very different route. This time because it will aid safety to outlaw things that are now considered a bad idea. If this goes anywhere its nearest ship vehicle is C++ 29.
Now, Python 2 to Python 3 did take a few years, maybe a decade. But just shipping the mechanism to reform C++ looks likely to take at least nine years. Not the reform, just the mechanism to enable it.
So what does the compiler chose, the edition of the caller, or the caller?
Crate A uses edition X.
Crate B uses edition Y + N, which has broken Rust language semantics between those editions, or changed a standard library type that Crate A uses on its public API in a backwards incompatible way.
Now an application on edition Y + M, with M >= N, would like to depend on Crate A and Crate B, and calling that incompatible API would be a requirement for the implementation goal.
So far editions haven't bothered covering such cases, deemed as not worthwhile thinking about.
Those in such scenarios get to hunt for another crate to replace Crate A, or have a local fork.
I’m not very sure, but maybe something like unforgettable types? Or linear types? Maybe negative trait bounds, or specialization?
People keep bringing this up when discussing backwards compatibility breaks, but I think the conclusion should be a bit more nuanced than just "backwards compatibility break <=> years (decades?) of pain".
IMHO, the problem was the backwards compatibility break coupled with the inability to use Python 2 code from 3 and vice versa. This meant that not only did you need to migrate your own code, but you also needed everything you depend on to also support Python 3. This applied in reverse as well - if you as a library developer naively upgraded to Python 3, that left your Python 2 consumers behind.
Obviously the migration story got better over time with improvements to 2to3, six, etc., that allowed a single codebase to work under both Python 2 and 3, but I think the big takeaway here is that backwards compatibility breaks can be made much more friendly as long as upgrades can be performed independently of each other.
The benefit of NOT maintaining backwards compatibility is that hopefully you end up with a cleaner language without all the baggage of the past, and "impedence mismatches" of new features that don't play well with old ones, etc, etc.
However, as a developer, I think I prefer the backwards compatible approach, knowing that:
1) Upgrading to latest version of the compiler hopefully isn't a big deal, and I can take advantage of any benefits that brings, including new language features, without having to sign up for rewriting existing tested code to remove deprecated features. In a large code base any such forced rewrites could be pretty onerous, and may involve a lot of retesting and potential for introducing bugs into code that was previously working.
2) It's nice to be able to take older projects, that you haven't worked on in a while (whether hobby projects, or legacy corporate code) and have the code still compile with the latest installed tool set, rather than to have developed "bit rot" due to using language features that have since been deprecated. Of course bit rot may also occur due to library changes, so language backwards compatibility is no guarantee of smooth sailing.
Maybe going forwards, with AI coding tools, this will become less of an issue, if they become capable of this sort of codebase updates without introducing errors. In fact, going forwards it may well be that choice of programming languages itself becomes seen as more something the tooling takes care of. Nowadays we write in high level languages and don't really think or care about what the generated machine code looks like. Maybe in the future we can write in high level instructions to the AI, without even having to care what the implementation language is?