Stl Container with Std::Unique_Ptr's VS Boost::Ptr_Container

stl container with std::unique_ptr's vs boost::ptr_container

They really solve two similar but different problems.

A pointer container is a way to store objects in a container that just so happen to be pointers to allocated memory rather than values. They do everything in their power to hide the fact that they are a container of pointers. This means:

  • Entries in the container cannot be NULL.
  • Values you get from iterators and functions are references to the type, not pointers to the type.
  • Working with many standard algorithms can be... tricky. And by "tricky", I mean broken. Pointer containers have their own built-in algorithms.

However, the fact that pointer containers know that they're containers of pointers, they can offer some new functionality:

  • A clone member function that performs a deep copy, via the use of a certain "Cloneable" concept on the type of the object.
  • The ability of a container to release ownership of its objects (after a shallow copy, for example).
  • Built-in functions to transfer ownership to other containers.

They really are quite different concepts. There is a lot of stuff you would have to do manually that pointer containers can do automatically with specialized functions.

If you really need a container of pointers, then you can use containers of unique_ptr. But if you need to store a bunch of objects that you happen to heap allocate, and you want to play special games with them involving ownership and such, then the pointer containers are not a bad idea.

Should we prefer vectorunique_ptr or boost::ptr_vector

I would suggest vector<unique_ptr<>> as it is supported by compiler. no extra effect. otherwise, boost need your import into your project.

boost::ptr_container and std::vectorshared_ptr

You are right, the two are widely different.

The first difference, as you noticed, is the ownership semantics. The ownership of items in a Pointer Container is NOT shared. In this regard, a boost::ptr_vector<T> is much closer to a std::vector<std::unique_ptr<T>>.

But this is not the only difference!

  • unless explicitly stated in the type, a Pointer Container will not contain any null pointer
  • a Pointer Container has deep copy semantics (using the new_clone method), and can only be copied if the object held is copyable
  • a Pointer Container has deep const semantics, that is if the container is const then one cannot mutate one of its element.

As for why @timday felt compelled to mention Boost Pointer Container, I think it's because he wanted to broaden the question somewhat. Boost Pointer Container are very much like Smart Pointers that could hold multiple objects, and provide a nicer syntax that containers of pointers in general.

Regarding his comparison to a std::vector< boost::shared_ptr<T> > I think it is simply because this is the traditional way of implementing a vector of pointers in the absence of move semantics (no unique_ptr) since auto_ptr cannot be used in STL container. People just don't know about Pointer Containers most of the time...

Boost.Pointer Container made obsolete by std::unique_ptr in C++11/14?

As James mentions in his answer, the Boost.Pointer containers offer a more intuitive interface as compared to what you get by sticking a unique_ptr into a standard library container.

Aside from that, boost::ptr_vector<T> (and friends) store the pointed to type as a void * underneath, so you don't get an entire class template instantiation for every T. This is not the case with vector<unique_ptr<T>>.

std::reverse on boost::ptr_vector slices objects?

One possible work around is to use

std::reverse(v.base().begin(), v.base().end());

instead. It reverses the pointers instead of the self-indirecting iterators.

How to pass a container of unique_ptrs where container, ptrs and objects are not modifiable?

I didn't want to include boost and span wouldn't work, because as @Jens pointed out, a unique_ptr doesn't propagate the cv qualifiers. Also, even if I did include boost, I wouldn't be able to get an actual object reference for each item in the vector, which I would need to allow me to compare relative locations of the object with others in the container.

So I opted instead for writing a wrapper over std::unique_ptr which will propagate the cv qualifiers.

The following is an excerpt from my enable_if.h file, which I use for the comparison operators to limit how many times I have to write them:

namespace detail
{
// Reason to use an enum class rather than just an int is so as to ensure
// there will not be any clashes resulting in an ambiguous overload.
enum class enabler
{
enabled
};
}
#define ENABLE_IF(...) std::enable_if_t<__VA_ARGS__, detail::enabler> = detail::enabler::enabled
#define ENABLE_IF_DEFINITION(...) std::enable_if_t<__VA_ARGS__, detail::enabler>

Here is my implementation of c++20's std::remove_cvref_t:

template <typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;

And here is the wrapped unique ptr:

template <typename T, typename D = std::default_delete<T>>
class unique_ptr_propagate_cv;

namespace detail
{
template <typename T, typename D>
std::unique_ptr<T, D> const& get_underlying_unique_ptr(unique_ptr_propagate_cv<T, D> const& object)
{
return object.ptr;
}
}

template <typename T, typename D>
class unique_ptr_propagate_cv
{
template <typename T_, typename D_>
friend std::unique_ptr<T_, D_> const& detail::get_underlying_unique_ptr<T_, D_>(unique_ptr_propagate_cv<T_, D_> const&);

using base = std::unique_ptr<T, D>;
base ptr;
public:
template <typename...Ts>
unique_ptr_propagate_cv(Ts&&...args) noexcept : ptr(std::forward<Ts>(args)...) {}

using element_type = typename base::element_type;
using deleter_type = typename base::deleter_type;

using pointer = element_type *;
using pointer_const = element_type const *;
using pointer_volatile = element_type volatile *;
using pointer_const_volatile = element_type const volatile *;

using reference = element_type &;
using reference_const = element_type const &;
using reference_volatile = element_type volatile &;
using reference_const_volatile = element_type const volatile &;

pointer get() noexcept { return ptr.get(); }
pointer_const get() const noexcept { return ptr.get(); }
pointer_volatile get() volatile noexcept { return ptr.get(); }
pointer_const_volatile get() const volatile noexcept { return ptr.get(); }

pointer operator->() noexcept { return ptr.get(); }
pointer_const operator->() const noexcept { return ptr.get(); }
pointer_volatile operator->() volatile noexcept { return ptr.get(); }
pointer_const_volatile operator->() const volatile noexcept { return ptr.get(); }

reference operator[](size_t index) noexcept { return ptr.operator[](index); }
reference_const operator[](size_t index) const noexcept { return ptr.operator[](index); }
reference_volatile operator[](size_t index) volatile noexcept { return ptr.operator[](index); }
reference_const_volatile operator[](size_t index) const volatile noexcept { return ptr.operator[](index); }

reference operator*() noexcept { return ptr.operator*(); }
reference_const operator*() const noexcept { return ptr.operator*(); }
reference_volatile operator*() volatile noexcept { return ptr.operator*(); }
reference_const_volatile operator*() const volatile noexcept { return ptr.operator*(); }

template <typename T_>
unique_ptr_propagate_cv& operator=(T_&& rhs)
{
return static_cast<unique_ptr_propagate_cv&>(ptr.operator=(std::forward<T_>(rhs)));
}

decltype(auto) get_deleter() const noexcept { return ptr.get_deleter(); }
operator bool() const noexcept { return ptr.operator bool(); }
decltype(auto) reset(pointer ptr = pointer()) noexcept { get_base_nonconst().reset(ptr); }
decltype(auto) release() noexcept { return get_base_nonconst().release(); }

};

template <typename T>
struct is_unique_ptr_propagate_cv : std::false_type {};

template <typename T, typename D>
struct is_unique_ptr_propagate_cv<unique_ptr_propagate_cv<T, D>> : std::true_type {};

namespace detail
{
inline nullptr_t const& get_underlying_unique_ptr(nullptr_t const& object)
{
return object;
}

template <typename T, typename D>
std::unique_ptr<T, D> const& get_underlying_unique_ptr(std::unique_ptr<T, D> const& object)
{
return object;
}
}

template <typename L, typename R
, ENABLE_IF(
is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
|| is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
)
>
bool operator==(L&& lhs, R&& rhs) noexcept
{
return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
== detail::get_underlying_unique_ptr(std::forward<R>(rhs));
}

template <typename L, typename R
, ENABLE_IF(
is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
|| is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
)
>
auto operator!=(L&& lhs, R&& rhs) noexcept
{
return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
!= detail::get_underlying_unique_ptr(std::forward<R>(rhs));
}

template <typename L, typename R
, ENABLE_IF(
is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
|| is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
)
>
bool operator<=(L&& lhs, R&& rhs) noexcept
{
return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
<= detail::get_underlying_unique_ptr(std::forward<R>(rhs));
}

template <typename L, typename R
, ENABLE_IF(
is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
|| is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
)
>
bool operator>=(L&& lhs, R&& rhs) noexcept
{
return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
>= detail::get_underlying_unique_ptr(std::forward<R>(rhs));
}

template <typename L, typename R
, ENABLE_IF(
is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
|| is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
)
>
bool operator<(L&& lhs, R&& rhs) noexcept
{
return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
< detail::get_underlying_unique_ptr(std::forward<R>(rhs));
}

template <typename L, typename R
, ENABLE_IF(
is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
|| is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
)
>
bool operator >(L&& lhs, R&& rhs) noexcept
{
return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
> detail::get_underlying_unique_ptr(std::forward<R>(rhs));
}

Thanks for your help and reminding me that it was just a propagation issue.

C++ Sharing elements in a boost::ptr_container?

Yes, you should use a vector<shared_ptr<int>>.

Since you're only working with ranges, you could put together a custom solution that keeps track of the ranges and their intersections (for implementing unique.) Then, you could store everything in vector and have the ranges index into that. This would probably be faster (just on account of the avoided cache misses), but it would be more work to implement.



Related Topics



Leave a reply



Submit