Regardless, I think lifetime annotations would solve far more problems than std::box. I really do like box as a suggestion as it would help clean up types, make things a bit more explicit in a few places, but there are bigger issues with C++ right now. This is a great suggestion (as is unique_resource for similar on the stack), but a relatively minor thing in the scheme of things. Still nice.
It isn't as if Microsoft, Apple and Google haven't been doing it for a while.
https://devblogs.microsoft.com/cppblog/high-confidence-lifet...
Which one of these (which implement the same copy/move semantics) would you prefer?
struct A {
box<int> thing;
}
struct B {
std::unique_ptr<int> thing;
B(std::unique_ptr<int> t) :
thing(std::move(t))
{}
B(const B& other) {
*this = other;
}
B& operator=(const B& other) {
if (other.thing)
thing = std::make_unique<int>(*other.thing);
else
thing.reset();
return *this;
}
B(B&&) = default;
B& operator=(B&&) = default;
~B() = default;
};
edit: nullptr checks.This is pretty much impossible when holding a pointer of base class. However, this is a primary reason for having pointers in the first place (polymorphism, and having abstract base classes).
In all other cases, you're probably better off with either the raw value, std::variant or std::reference_wrapper.
For example shared-ptr to base can correctly invoke the correct derived type destructor even if the destructor is not virtual.
Edit: accidentally a word.
> For example shared-ptr to base can correctly invoke the correct derived type
Invoke what exactly? Im sorry I don't understand what you're trying to say here.
I guess you can force all derivied types to implement a clone() function, such that box<T> can do the deep copy, but Id consider that a fairly big inconvenience for such a simple pointer type.
I find it a delight
I'm not a big fan of RTTI, and not even sure if it would work here. But once you start keeping track of all derived types, you might as well use an std:: variant. It's more cache friendly too, so more performant in many cases.
https://hackernoon.com/value-ptr-the-missing-c-smart-pointer...
https://buckaroo.pm/blog/value-ptr-the-missing-smart-ptr
People have been writing pointer-like value semantic wrappers for type-erasure for decades.
Indeed it's exactly the same idea (and I think `value_ptr` is actually a better name than `box`).
I've googled before writing the blog post and haven't found literature about this (but I was mostly googling stuff around "box" or "deep shared ptr").
Thank you for the link! Kinda glad it's more widespread than what I though actually, it means we can think about making an official proposal.
A Proposal for the World's Dumbest Smart Pointer, v4 - Open Standards https://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4282.p...
I think if you come from other languages you assume the heap is the default when it should be the exception.
stack is usually quite limited unless you very much control the environment
I don't understand why the author writes "raw value is straightforward and efficient... However, you can't allocate them dynamically and you can't build recursive data structure such as a linked list or a tree with them." There is clearly something I don't understand here. Consider an int -- you can dynamically allocate one, you can put it in a tree. Putting a box into a tree will still require other data applicable to the tree itself, same as an int), and so on. So I don't understand the point being made here.
And the deep copy behavior is rarely what I want in a mutable structure anyway (it's always safe, if usually wasteful, in a R/O structure).
To make a tree, you need something allowing an object (a tree node) to own an object of the same type as itself (another tree node). There are various options for this: raw pointer, unique_ptr, shared_ptr, auto_ptr, box. Without using one of those options, the tree isn't possible. That's the author's point. std::box can be used in place of one of the other pointer types.
so, is it the pointer that is heap-allocated or the pointee? frankly, i find this article somewhat incoherent, and importantly, it lacks code examples illustrating what it is talking about.
I think this would be way too big of a footgun: with implicit copy it would be too easy to pass box instead of box& and accidentally make copies when you didn't mean to. Box is not copy in Rust which avoids that problem, so really the equivalent in C++ would be just to add a .deepcopy() function on uniqueptr which is only implemented if the underlying type has a copy ctor.
also, c++ has (wisely, imho) rejected the concept of a "deep copy" - we just have copies, of varying depths.
non_null_unique_ptr<T>
that is enforced at compile time would be far more valuable for me. That would mean some kind of destructive move where the compiler guarantees that you can not access a moved from object.If your build system respects ClangTidy checks and turn them into errors, it's effectively the same as a compiler guarantee.
EDIT: Why are you booing me? I'm right.
No C++ smart pointer has "value semantics", relative to its target T. You can see this because == performs address comparison, not deep comparison, and `const` methods on the smart pointer can be used to mutate the target (e.g. in C++, operator* on unique_ptr is always const, and yields a T&).
This is in contrast to Rust, where Box performs deep equality, and has deep const/mut. In Rust, Box is basically just a wrapper around a value to have it on the heap (enabling things like dynamic polymorphism, like in C++). In C++, the pointer is its own entity, with its own separate equality, and so on.
Const-ness of operations, operator==, and assignment/copying behavior all have to be consistent with each other. For example, if `box` was simply `unique_ptr` with a copy constructor (somehow, and as the table in the blog post basically implies), then you would have that after `auto a = b;`, `a != b`, which obviously doesn't work. This means that the hypothetical `std::box` would have to have its comparison and const-ness adjusted as well. In C++ terms, this isn't really a pointer at all. The closest thing to what the author is suggesting is actually `polymorphic_value`, I believe, which IIRC has been proposed formally (note that it does not have pointer in the name).
Also as an aside, smart pointers are not suitable a) for building data structures in general, and b) building recursive data structures in particular. The former is because meaningfully using smart pointers (i.e. letting them handle destruction) inside an allocator aware data structure (as many C++ data structures tend to be, and even data structures in Rust) would require duplicating the allocator over and over. The latter is because compilers do not perform TCO in many real world examples (and certainly not in debug mode); if you write a linked list using `std::unique_ptr` the destructor will blow your stack.
Not quite clear what duplicating means here, but in general most smart_pointers can be constructed with an optional "deleter". Well implmented this would results into the addition one reference (64 bits) field to each instance of the smart_pointer (remove that by using some static member kung-fu but this is hardly worth it).
> The latter is because compilers do not perform TCO in many real world examples (and certainly not in debug mode); if you write a linked list using `std::unique_ptr` the destructor will blow your stack.
This true for all deeply nested structure. Same thing happen in reference counted system like swift. One can mitigate this by simply controlling the order of destruction.
If you control the order of destruction, then you're just manually asking for things to be destroyed, and not actually making use of the smart pointers main functionality. Why use them at that point? That's why I also used the phrase "meaningfully" use them earlier.
Look inside the STL, boost, abseil, etc. You'll very rarely see smart pointers used to implement containers/data structures.
From what i understand the key here is that authors seem to be mixing references and pointers.
What the author wants here (std::box) is a garbage collected/RAII'ed reference to a heap allocated object.
smart_pointers are pointers... which is to say their identity is distinct from pointed object.
I don't think std::box is missing as much as it doesn't really fit well in C++ current memory model, and distinction between references, pointer and ownership.
The question is not about the utility of box<T>, i am sure there are cases where such a structure would be need.
But box<T> is not a "pointer" in the C++ sense same as an array/vector of 1 is not a pointer.
The authors is needlessly complicating things and mixing concept there are pretty orthogonal. And i think this is the result of trying to fit box<T> which is not a pointer inside a taxonomy of pointers...
In Rust you will love unless you write `Clone`.
In Rust, it's move by default and you need an explicit .clone()
In C++, it's copy by default and you need an explicit std::move()
Rust requires types to explicitly opt-in to being implicitly copied, while C++ requires you to opt-out by deleting the copy-constructor.
Accidentally copying small structs on the stack is a minor performance problem. Copying an std::box<int> in a hot loop could cause heap fragmentation, lock contention and huge amounts of wasted memory due to heap alignment requirements (32 bytes on 64-bit arches).
The problem is already there, Box wouldn't change anything.
if it is such a solution to such a common problem (both of which i dispute), why do you think it is not alredy in the standard library?
C++ has a concept of not paying for what you don't use. So if you only need one of that type then the vector isn't the right thing to use because the vector will have dynamic array-sizing information: at the very least, two size_t's one to indicate allocated size and one to indicate used size.
Then, a vector won't deep-copy a pointer. So a Vector requires the concrete most-derived type, and not a pointer to it. That's not the same as pointing to a base class to avoid needing to know the most-derived type.
No, I don't agree that a vector<type> is a good 'box' like container.
I think the underlying cause is that Rust's assignment semantic is a destructive move, not a copy†, which frees up the opportunity for an actual copy to be potentially expensive, matching reality. In a language where assignment is copy, that operation must be cheap and so we've obliged to make up an excuse for how although this is a "copy" it doesn't behave the way you want, it's just a "shallow copy".
† Although it will nearly always work to think of Rust's assignments as destructive move, as an optimisation types whose representation is their meaning can choose to implement Copy, a trait which says to the compiler that it's fine to actually just copy my bits, I have no deeper meaning - thus if the type you're using is Copy then assignments for that type are in fact performed just by copying and don't destroy anything. So a byte, a 64-bit floating point number, a 4CC, an IP address none of those have some larger significance, they're Copy, but a string, a HashMap, some custom object you made (unless it can and did opt in to Copy), those are not Copy.
Crucially, from an understanding point of view. Implementing Copy requires a trivial implementation of Clone. As a result it feels very natural.
There is no builtin deep copy facility. Without the facility then a box pointer would be dangerous leading to weird effects when the copy is too shallow.
You could solve deep copy with a template that relies on each class providing a deep copy function if one is needed. But again, this will make bugs if someone forgets to provide the function.
Rather than make an error-prone feature in the standard library, I think it would be better to just explicitly roll this yourself. A sensible constructor copy should already do a deep copy -- or ensure copy-on-write to simulate a deep copy. So copying is as easy a calling make_shared (original) or make_unique (original).
That doesn’t seem to fix a memory bug (cause doing this with a unique ptr, then the compiler would yell at you for using copy), it seems to just make it easier than having to write `std::make_unique(*otherptr);`
I also think the semantics of shared and unshared const and mutable state need to be made explicit. Pony is very good about this more so than Rust by bringing into the language.
virtual std::flexptr<Thing> get_expensive_thing();
vs: virtual bool has_expensive_thing();
virtual const Thing& get_expensive_thing();
virtual std::unique_ptr<Thing> build_expensive_thing();
It'd probably have shared_ptr semantics but you'd have to treat it as a const ref for lifetime purposes, which might make it distasteful to the std library folks.But I doubt many people would use it, and that’s probably why it doesn’t belong in std::.
In contrast, before C++ 11, developers would write their own RAII-style smart pointers. So it made sense to save them the labor. I don’t think a pointer that doesn’t allow shallow copies is usually found in codebases. It sounds like a specific use-case pointer.
It’s a neat type that people coming from other languages could like, but maybe not quite standard library-ready?
The lack of this type can be viewed as a pessimization for copying objects.
Taking that aside, I agree it would make a lot of sense to write code in that style^^