You still need to dereference the `value_ptr` a lot, right? Otherwise argument dependent lookup won't work for the wrapped value type.
I guess you could provide specializations for all the common candidates (begin, end, swap, size, empty, data, and whatever gets added in the future) in the "valuable" namespace, but you'll need to be pretty aggressive with adding new specializations as people find ones they need.
Also, it might be a good idea to support a Deleter template parameter and propagate it to the underlying `unique_ptr`. Some projects use that customization point for various purposes, like instrumentation of (de)allocation or grabbing certain kinds of objects from memory pools.
The idea of a value_ptr is something that's been around in C++ for quite some time, now. It usually goes by the name clone_ptr or copy_ptr. (Googling for either will yield several implementations.)
Looking at the author's code on GitHub, this implementation seems decent.
One thing to note about this implementation is that it doesn't address the other big reason for doing dynamic allocations: polymorphic types. Value semantics are preserved so faithfully that objects are sliced just as they would be if they weren't dynamically allocated.
For similar reasons it seems inappropriate for PIMPL. The template indirectly calls `sizeof(...)` on the template type. When that type is not concrete (which is likely the case with PIMPL) the code will not compile. The `value_ptr_example_pimpl.hpp` example doesn't compile for me using GCC 5.4.
Well, that isn't a flaw in the implementation of value_ptr. The example, as provided, won't compile because the the copy constructor, assignment operator, and destructor are declared `= default` in the header. This causes any translation units that include value_ptr_example_pimpl.hpp to attempt to stamp out the default implementation, which does require knowing `sizeof(Foo::Pimpl)`.
Moving those methods to value_ptr_example_pimpl.cpp, just as you'd normally do for PIMPL makes the compiler happy.
If you don't copy the resource then you are sharing it.
This means you'll have reference semantics and every instance operates on the same resource. Value_ptr implements value semantics and so it behaves like primitives.
> If you don't copy the resource then you are sharing it.
I think their point is more that the copy has an arbitrary complexity, so while you think you're copying a smart pointer you're triggering a possibly huge cascade behind the scenes.
> This means you'll have reference semantics and every instance operates on the same resource
You could have an explicit copy operation on an otherwise move-semantic smart pointer. No sharing, and while you get the ability to copy stuff if necessary it's not potentially hidden behind every argument passing or assignation, it becomes a very deliberate operation.
not_null style classes might be what you're after, e.g. https://github.com/Microsoft/GSL/blob/master/include/gsl/gsl.... Whan trying to store nullptr in it at runtime that will result in an exception or abort or whatever it's implemented like. Depending on the usecase, it's still better than manually checking for nullptr though.
moving from a pointer must give a valid object
Yes (well, actually it's state is 'valid but unspecified') but afaik performing any operations having preconditions on moved-from objects is undefined behaviour anyway so you shouldn't even be doing that. And when using not_null that would again result in an exception.
Yes, but it is a bit awkward. You can can separate implementations depending on whether T has a particular member function. I need to look up how every time, though.
> You can can separate implementations depending on whether T has a particular member function.
Separate implementation, but not separate interface? The Rust bit means Box is "transparently" clonable, meaning it only implements the clone trait(/interface) if the value it wraps also does, trying to clone a Box<T> where T can not be cloned would be a compile-time error.
One thing is using smart pointers "a la RAII", and another, "creative, a la dangerous, usage". E.g. some of the risks of using smart pointers from a previous HN discussion: https://news.ycombinator.com/item?id=11698784
30 comments
[ 4.0 ms ] story [ 71.5 ms ] threadI guess you could provide specializations for all the common candidates (begin, end, swap, size, empty, data, and whatever gets added in the future) in the "valuable" namespace, but you'll need to be pretty aggressive with adding new specializations as people find ones they need.
Also, it might be a good idea to support a Deleter template parameter and propagate it to the underlying `unique_ptr`. Some projects use that customization point for various purposes, like instrumentation of (de)allocation or grabbing certain kinds of objects from memory pools.
Looking at the author's code on GitHub, this implementation seems decent.
One thing to note about this implementation is that it doesn't address the other big reason for doing dynamic allocations: polymorphic types. Value semantics are preserved so faithfully that objects are sliced just as they would be if they weren't dynamically allocated.
Consider this example: https://gist.github.com/SeanCline/c81218e4c0208ccb871268aecd...
Moving those methods to value_ptr_example_pimpl.cpp, just as you'd normally do for PIMPL makes the compiler happy.
This works for me in VS2017 but should work on any C++11 compliant compiler: https://gist.github.com/SeanCline/55d700d4fbf8cc1bdaeb44b547...
I wish C++ had a unique_ptr without the ability to be null. But that does not seem possible given that moving from a pointer must give a valid object.
I think their point is more that the copy has an arbitrary complexity, so while you think you're copying a smart pointer you're triggering a possibly huge cascade behind the scenes.
> This means you'll have reference semantics and every instance operates on the same resource
You could have an explicit copy operation on an otherwise move-semantic smart pointer. No sharing, and while you get the ability to copy stuff if necessary it's not potentially hidden behind every argument passing or assignation, it becomes a very deliberate operation.
moving from a pointer must give a valid object
Yes (well, actually it's state is 'valid but unspecified') but afaik performing any operations having preconditions on moved-from objects is undefined behaviour anyway so you shouldn't even be doing that. And when using not_null that would again result in an exception.
An alternative option would be a unique_ptr subtype (or extension) which allows cloning if the wrapped type is clonable, something similar to rust's
Can that be expressed without concepts?Separate implementation, but not separate interface? The Rust bit means Box is "transparently" clonable, meaning it only implements the clone trait(/interface) if the value it wraps also does, trying to clone a Box<T> where T can not be cloned would be a compile-time error.
unique_ptr and shared_ptr are really simple and incredibly useful. If you're too scared to use them, you should not be using C++ at all.
Is it just about heap vs. stack?
Herb Sutter: Leak-Freedom in C++ by default