How can I use unique_ptr with a more generic deleter? - c++

Consider some function:
template<typename F>
void foo(F f) {
std::unique_ptr<int> p = f();
// do some stuff with p
}
Because unique_ptr decrees a default template argument, default_delete, for D, any function object passed to foo that returns a unique_ptr with a non-default deleter fails to compile. For example,
int x = 3;
foo([&x](){
// use empty deleter
return std::unique_ptr<int>(&x, [](int*){});
});
However, I could see this being potentially useful, and I don't see a direct reason why it shouldn't be possible. Is there a common approach for addressing this?
Edit
The easy fix would be to define foo instead to use the following:
std::unique_ptr<int, std::function<void(int*)>> p = f();
But I'm wondering why this couldn't have been incorporated into the interface for unique_ptr? Is there a reason the class interface couldn't provide this generic attribute? Are there approaches for "wrapping" this kind of thing into a new definition?
For example,
template<typename T>
using Generic_unique_ptr =
std::unique_ptr<
T,
std::function< void(typename std::unique_ptr<T>::element_type*) >
>;
But this seems dangerous because it exposes the potential to do something like the follwing,
Generic_unique_ptr<int> p(new int());
which would leave the deleter uninitialized and exhibit undefined behavior. Perhaps some way to provide an instance of std::default_delete<T> as the default deleter?

If all you want to do is use the pointer in a function, you can just
use the auto keyword; the compiler will deduce the type of unique_ptr
which has been used and thus automatically do the right thing:
template <typename F>
void foo(F f)
{
auto p = f();
p->bar();
}
Now, from your comment, we know that this is not all you want, but you
want to be able to store the unique_ptr in your class to work with
it later. This creates a set of completely different problems:
unique_ptr<T, D1> and unique_ptr<T, D2> are different types. Thus we need to know what unique_ptr<T, D> will be returned by your functor F
Even if we knew the return type of F in advance, our class can still only store unique_ptr<T, D1> and not unique_ptr<T, D2>.
The easiest way around this (that I can think of, there might be better
ways) is type erasure.
We create ourselves a base class that exposes the pointer managed by the
unique_ptr:
template <typename T>
struct wrapper
{
virtual ~wrapper() {}
virtual T const * get() const = 0;
virtual T * get() = 0;
};
From that class inherits our actual storage class, which deduces the type
of unique_ptr:
template <typename T, typename F>
struct storage
: wrapper<T>
{
storage(F f) { p_ = f(); }
T const * get() const { return p_.get(); }
T * get() { return p_.get(); }
private:
typename std::result_of<F()>::type p_;
};
In the class you actually care about, you can now store a pointer to our
base class and use polymorphism to access the underlying object, in this
case the unique_ptr. Assume we moved the classes above into
namespace detail to hide them from the user:
template <typename T>
class some_class
{
public:
template <typename F>
void store(F f)
{
storage_.reset(new detail::storage<T, F>(f));
}
T const * get() const { return storage_->get(); }
T * get() { return storage_->get(); }
private:
std::unique_ptr<detail::wrapper<T>> storage_;
};
You can find a fully working example here.

But I'm wondering why this couldn't have been incorporated into the interface for unique_ptr?
Because to do so would force all of std::function's overhead onto everyone. unique_ptr is intended to be useful for pretty much any case of single ownership of a pointer. You pay for what you use; not everyone who uses a custom deleter needs that deleter to be generic. This way, they don't have to pay for it.
Also, the current methodology allows it to handle non-pointer resources, as the deleter can specify exactly what type gets stored in the unique_ptr.
If you want to provide this generic deleter construct, you could create a class that (privately) inherits from unique_ptr and replicates its interface, minus the constructor that doesn't take a deleter instance. That way, the user is forced to pass a deleter function in.

Related

Function that accepts any pointer type

I have a function void Foo(MyType* mt). I want callers to be able to pass any pointer type to this function (e.g. unique_ptr, shared_ptr or iterator) and not require passing a raw pointer. Is there any way to express this? I could write:
template <typename T>
void Foo(T t);
This will work since it will only compile if T supports operator-> and operator* which I use inside Foo, it will also only work if T has the same interface as MyType but it seems wrong to not specify in the API that I expect that template argument to be a pointer to MyType. I could also write my own wrapper:
template <typename T>
class PointerWrapper {
public:
PointerWrapper(T* t) : raw_ptr(t) {}
PointerWrapper(const std::unique_ptr<T>& t) : raw_ptr(t.get()) {}
...
private:
T* raw_ptr;
};
void Foo(PointerWrapper<MyType> mt);
This seems clunky because I will need to extend PointerWrapper for every smart pointer type under the sun.
Is there an accepted way to support this?
In C++20, you'd write a concept such as
template <typename P, typename T>
concept points_to = requires(P p) {
{ *p } -> std::common_reference_with<T &>
} && std::equality_comparable_with<std::nullptr_t>
template <points_to<MyType> T>
void Foo(T t);
Prior to that, you could write something involving std::pointer_traits
template <typename T>
std::enable_if_t<std::is_same_v<MyType, typename std::pointer_traits<T>::element_type>> Foo(T t);
I want callers to be able to pass any pointer type to this function (e.g. unique_ptr, shared_ptr or iterator
Don't.
Each kind of smart (or dumb) pointer has a very specific purpose, illustrated below.
void foo(std::shared_ptr<T>); // I will assume joint ownership
// that may or may not outlive the call
void foo(std::unique_ptr<T>); // I will take your ownership away
// You better not be needing it anymore
void foo(T*); // I am just borrowing it
// Whoever owns it should not worry
It makes little sense for a function to either take ownership or not, depending on what kind of pointer the user passes.
And, naturally, if your function does not do any iterating things, it should not take iterators.

C++ static member function vs lambda overhead

I have some kind of templated base class
template<typename Derived>
class Base { };
and want to store derived instances of it in a list.
For that I use a using derived_handle = std::unique_ptr<void, void(*)(void*) alias.
When I now add a derived instance to the list i cound use a static member function as deleter
class foo {
template<typename Derived, typename... Args>
void add_base(Args&&... args) {
auto derived = derived_handle{new Base{std::forward<Args>(args)..., &foo::_deleter<Derived>};
_derived.emplace_back(std::move(derived));
}
private:
template<typename Baser>
void _deleter(void* base) {
delete static_cast<Base*>(base);
}
std::vector<derived_handle> _derived{};
};
or a lambda
class foo {
template<typename Derived, typename... Args>
void add_base(Args&&... args) {
auto deleter = [](auto* derived){
delete static_cast<Derived*>(derived);
}
auto derived = derived_handle{new Base{std::forward<Args>(args)..., std::move(deleter)};
_derived.emplace_back(std::move(derived));
}
private:
std::vector<derived_handle> _derived{};
};
Are there any advantages/disadvantages of the lambda version I should be aware of?
Time for a frame challenge!
You've made some bad decisions in that code. Most people who use unique_ptr, even in a polymorphic context, don't need custom deleters at all. The only reason you do, is because of your type erasure, and that's only there because Base<A> and Base<B> are unrelated types.
If you really need Base<T>, have it inherit from an actual polymorphic (and non-templated) base class with a virtual destructor. Then you don't need unique_ptr<void> (a really bad code smell), and you can actually use your list in a type-safe manner.

Pass object with derived template to function that accepts object with base template

How do I pass an object with a derived template instantiation to a method accepting those objects with a base template instantiation?
It seems possible, as std::shared_ptr or std::pair seem capable to do it.
E.g.
#pragma once
#include <iostream>
#include <memory>
struct Base {
virtual void print() = 0;
};
struct Derived : public Base {
void print() {
std::cout << "Got it!" << std::endl;
}
};
void printBase(const std::shared_ptr<Base> &ptr){
ptr->print();
}
void printBase(const std::pair<Base&, Base&> &pr){
pr.first.print();
}
template <typename T>
struct Wrap {
T& t;
};
void printBase(const Wrap<Base> &wrap) {
wrap.t.print();
}
int main() {
Derived d;
std::shared_ptr<Derived> ptr = std::make_shared<Derived>(d);
printBase(ptr); // works
std::pair<Derived&, Derived&> pr = {d, d};
printBase(pr); // works
Wrap<Derived> w = Wrap<Derived>{d};
// printBase(w); // gives compile error
}
You will need to explicitly add a conversion constructor and/or assignment operator to your Wrapped type to be able to convert from different types.
This is how both std::shared_ptr and std::pair do this internally; shared_ptr<T> can be constructed from shared_ptr<U> types (with SFINAE restrictions that U* is convertible to T*), and pair<T,U> can be constructed from pair<T2,U2> types (with SFINAE restrictions that T2 is convertible to T and U2 is convertible to U).
Doing this can be as simple as adding a new constructor:
template <typename T>
struct Wrap
{
Wrap(T& ref)
: t{ref}
{
}
template <typename U, typename = std::enable_if_t<std::is_convertible_v<U&, T&>>>
Wrap(const Wrap<U>& other)
: t{other.t}
{
}
T& t;
};
The above example uses is_convertible as a condition for enable_if so the constructor is only visible when references of U can be converted to references of T. This will constrain it such that U must be hierarchically related to T (since references aren't convertible otherwise) -- which would allow a Wrapped<Derived> to be converted to Wrapped<Base>, but not vice-versa.
Edit: As mentioned in the comments, it's worth noting that, unlike types that are part of a hierarchy -- where a reference to Derived can be passed as a reference to Base, types that wrap hierarchies will not be able to pass a reference to a Template<Derived> as a reference to a Template<Base>.
The examples with a std::shared_ptr<Derived> being passed to a const std::shared_ptr<Base>& only actually work due to const-lifetime extension in C++. This doesn't actually pass it as a reference -- but instead materializes a temporary object of std::shared_ptr<Base> which gets passed to the reference. It's effectively the same as passing-by-value.
This also means that you cannot have a Template<Derived> be passed to a non-const Template<Base> reference, since lifetime extension only occurs for const references.
Edit: As discussed in the comments:
Making the constructor a copy conversion is not required; it could easily be an R-value constructor instead. However, if you are doing cleanup on destruction of the wrapped type, then you will need to somehow flag that the moved-from object does not need to be cleaned up. The easiest way is using pointers, where you rebind to nullptr after moving:
template <typename T>
struct Wrap
{
Wrap(T& ref)
: t{std::addressof(ref)}
{
}
Wrap(Wrap&& other)
: t{other.t}
{
other.t = nullptr;
}
Wrap(const Wrap&) = delete;
template <typename U, typename = std::enable_if_t<std::is_convertible_v<U&, T&>>>
Wrap(Wrap<U>&& other)
: t{other.t}
{
other.t = nullptr;
}
~Wrap() {
if (t != nullptr) {
cleanup(*t); // some cleanup code
}
}
T* t;
};
If pointers aren't possible for your desired API, then you may need to use a bool needs_cleanup that gets set appropriately during moves, since references cannot be rebound.
Note: if the data is private and not public as in this example, you may need to have a friend declaration of:
template <typename> friend class Wrap;
So that a Wrap<T> may access the private data members of a Wrap<U>.
Change printBase function to the following one:
template <typename T, typename = std::enable_if<std::is_base_of_v<Base, T>>>
void printBase(const Wrap<T> &wrap){
wrap.t.print();
}

Overload method for unique_ptr and shared_ptr is ambiguous with polymorphism

Coding stuff after taking the hint from my previous question's answer, I ran into an issue with overloading Scene::addObject.
To reiterate the relevant bits and make this self contained, with the least details possible:
I have a hierarchy of objects inheriting from Interface of which there are Foos and Bars;
I have a Scene which owns these objects;
Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
the main passes them to the Scene instance, which takes ownership.
Minimal code example is this:
#include <memory>
#include <utility>
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface
{
};
class Bar : public Interface
{
};
class Scene
{
public:
void addObject(std::unique_ptr<Interface> obj);
// void addObject(std::shared_ptr<Interface> obj);
};
void Scene::addObject(std::unique_ptr<Interface> obj)
{
}
//void Scene::addObject(std::shared_ptr<Interface> obj)
//{
//}
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
// auto bar = std::make_shared<Bar>();
// scn->addObject(bar);
}
Uncommenting the commented lines results in:
error: call of overloaded 'addObject(std::remove_reference<std::unique_ptr<Foo, std::default_delete<Foo> >&>::type)' is ambiguous
scn->addObject(std::move(foo));
^
main.cpp:27:6: note: candidate: 'void Scene::addObject(std::unique_ptr<Interface>)'
void Scene::addObject(std::unique_ptr<Interface> obj)
^~~~~
main.cpp:31:6: note: candidate: 'void Scene::addObject(std::shared_ptr<Interface>)'
void Scene::addObject(std::shared_ptr<Interface> obj)
^~~~~
Uncommenting the shared and commenting the unique stuff also compiles, so I take it the problem is, like the compiler says, in the overload. However I need the overload as both these types will need to be stored in some kind of collection, and they are indeed kept as pointers to base (possibly all moved into shared_ptrs).
I'm passing both by-value because I want to make clear I'm taking ownership in Scene (and upping the reference counter for the shared_ptrs). Not really clear to me where the issue lies at all, and I couldn't find any example of this elsewhere.
The problem you are encountering is this constructor of shared_ptr (13), (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr (6) (also not explicit).
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
13) Constructs a shared_ptr which manages the object currently managed by r. The deleter associated with r is stored for future deletion of the managed object. r manages no object after the call.
This overload doesn't participate in overload resolution if std::unique_ptr<Y, Deleter>::pointer is not compatible with T*. If r.get() is a null pointer, this overload is equivalent to the default constructor (1). (since C++17)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6) Constructs a unique_ptr by transferring ownership from u to *this, where u is constructed with a specified deleter (E).
This constructor only participates in overload resolution if all of the following is true:
a) unique_ptr<U, E>::pointer is implicitly convertible to pointer
b) U is not an array type
c) Either Deleter is a reference type and E is the same type as D, or Deleter is not a reference type and E is implicitly convertible to D
In the non polymorphic case, you are constructing a unique_ptr<T> from a unique_ptr<T>&&, which uses the non-template move constructor. There overload resolution prefers the non-template
I'm going to assume that Scene stores shared_ptr<Interface>s. In that case you don't need to overload addObject for unique_ptr, you can just allow the implicit conversion in the call.
The other answer explains the ambiguity and a possible solution. Here's another way in case you end up needing both overloads; you can always add another parameter in such cases to break the ambiguity and use tag-dispatching. The boiler-plate code is hidden in private part of Scene:
class Scene
{
struct unique_tag {};
struct shared_tag {};
template<typename T> struct tag_trait;
// Partial specializations are allowed in class scope!
template<typename T, typename D> struct tag_trait<std::unique_ptr<T,D>> { using tag = unique_tag; };
template<typename T> struct tag_trait<std::shared_ptr<T>> { using tag = shared_tag; };
void addObject_internal(std::unique_ptr<Interface> obj, unique_tag);
void addObject_internal(std::shared_ptr<Interface> obj, shared_tag);
public:
template<typename T>
void addObject(T&& obj)
{
addObject_internal(std::forward<T>(obj),
typename tag_trait<std::remove_reference_t<T>>::tag{});
}
};
Full compilable example is here.
You have declared two overloads, one taking std::unique_ptr<Interface> and one taking std::shared_ptr<Interface> but are passing in a parameter of type std::unique_ptr<Foo>. As none of your functions match directly the compiler has to perform a conversion to call your function.
There is one conversion available to std::unique_ptr<Interface> (simple type conversion to unique pointer to base class) and another to std::shared_ptr<Interface> (change to a shared pointer to the base class). These conversions have the same priority so the compiler doesn't know which conversion to use so your functions are ambiguous.
If you pass std::unique_ptr<Interface> or std::shared_ptr<Interface> there is no conversion required so there is no ambiguity.
The solution is to simply remove the unique_ptr overload and always convert to shared_ptr. This assumes that the two overloads have the same behaviour, if they don't renaming one of the methods could be more appropriate.
The solution by jrok is already quite good. The following approach allows to reuse code even better:
#include <memory>
#include <utility>
#include <iostream>
#include <type_traits>
namespace internal {
template <typename S, typename T>
struct smart_ptr_rebind_trait {};
template <typename S, typename T, typename D>
struct smart_ptr_rebind_trait<S,std::unique_ptr<T,D>> { using rebind_t = std::unique_ptr<S>; };
template <typename S, typename T>
struct smart_ptr_rebind_trait<S,std::shared_ptr<T>> { using rebind_t = std::shared_ptr<S>; };
}
template <typename S, typename T>
using rebind_smart_ptr_t = typename internal::smart_ptr_rebind_trait<S,std::remove_reference_t<T>>::rebind_t;
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface {};
class Bar : public Interface {};
class Scene
{
void addObject_internal(std::unique_ptr<Interface> obj) { std::cout << "unique\n"; }
void addObject_internal(std::shared_ptr<Interface> obj) { std::cout << "shared\n"; }
public:
template<typename T>
void addObject(T&& obj) {
using S = rebind_smart_ptr_t<Interface,T>;
addObject_internal( S(std::forward<T>(obj)) );
}
};
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
auto bar = std::make_shared<Bar>();
scn->addObject(bar); // ok
}
What we do here is to first introduce some helper classes that allow to rebind smart pointers.
Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
Can you overload in terms of pointer-to-Foo and pointer-to-Bar instead of pointer-to-Interface, since you want to treat them differently ?

Type erasure in C++: how boost::shared_ptr and boost::function work?

Type erasure - is that how you call it?
How boost::shared_ptr stores its deleter and how boost::function stores its function object?
Is there any tutorial that teaches the trick?
What is the run-time cost of using type-erased function objects?
The idea is simple, you define a base class that has an interface with the functionality you need, and then inherit from it. Since the type erased class uses only that interface, the actual type underneath is forgotten and erased. Alternatively, if the only needed interface can be expressed as free functions you can store pointers to the free functions.
namespace detail {
struct deleter_base {
virtual ~deleter_base() {}
virtual void operator()( void* ) = 0;
};
template <typename T>
struct deleter : deleter_base {
virtual void operator()( void* p ) {
delete static_cast<T*>(p);
}
};
}
template <typename T>
class simple_ptr {
T* ptr;
detail::deleter_base* deleter;
public:
template <typename U>
simple_ptr( U* p ) {
ptr = p;
deleter = new detail::deleter<U>();
}
~simple_ptr() {
(*deleter)( ptr );
delete deleter;
}
};
This is a really simplified smart pointer, but the idea is there. In the particular case of shared_ptr, the deleter is stored as part of the reference count object, that is held by pointer.