Как передать контейнер unique_ptrs, где контейнер, ptrs и объекты не могут быть изменены?
У меня есть контейнер vector
который имеет std::unique_ptr
какого-то типа. Я хочу вернуть этот контейнер, но также хочу убедиться, что я не хочу, чтобы контейнер, указатель или объект, на который указывает объект, были модифицируемыми. Я также не хочу делать несколько параллельных копий этого объекта. Мой тип псевдонима будет что-то вроде:
using container_t = vector<std::unique_ptr<my_type_t>>
Поэтому я думаю, что я мог бы сделать еще один псевдоним, как это:
using const_container_t = const vector<std::unique_ptr<const my_type_t>>
и сделать reinterpret_cast
для моего добытчика:
const_container_t& encompassing_type::get_container() const
{
return reinterpret_cast<const_container_t&>(m_container);
}
Я думаю, что это должно сработать, но мне интересно, есть ли какие-то ошибки, которых я не вижу, или есть какой-то другой лучший способ сделать это.
Я также предположил бы, что это может привести к дублированию двоичного кода в окончательной сборке, но, поскольку они, скорее всего, встроены в любом случае, это не должно быть проблемой.
2 ответа
Я не хотел включать повышение и span
не будет работать, потому что, как указал @Jens, unique_ptr
не распространяет квалификаторы cv. Кроме того, даже если бы я включил надстройку, я не смог бы получить фактическую ссылку на объект для каждого элемента в векторе, что мне понадобилось бы, чтобы я мог сравнивать относительные местоположения объекта с другими в контейнере.
Поэтому я решил вместо этого написать обертку std::unique_ptr
который будет распространять квалификаторы cv.
Ниже приводится выдержка из моего enable_if.h
файл, который я использую для операторов сравнения, чтобы ограничить, сколько раз я должен написать их:
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>
Вот моя реализация с ++20-х годов std::remove_cvref_t
:
template <typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
А вот и завернутый уникальный 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));
}
Спасибо за вашу помощь и напомнил мне, что это была просто проблема распространения.
Проблема в том, что std::unique_ptr::operator*
определяется для возврата неконстантной ссылки:
std::add_lvalue_reference<T>::type operator*() const
Поскольку это внутренний класс, вы можете использовать простые указатели и явно управлять временем жизни, что позволяет вам делать что-то вроде
span<my_type_t const> encompassing_type::get_container() const
{
return span( m_container );
}
Джастин предлагает использовать что-то вроде span<const my_type_t>
реализовать представление постоянных указателей на ваш вектор. Например, вы можете сделать это с Boost.Range и вернуть диапазон константных указателей:
#include <boost/range.hpp>
#include <boost/range/adaptor/transformed.hpp>
using namespace boost::adaptors;
class X {
public:
void nonConst() {}
void constF() const {}
};
class A{
std::vector<std::unique_ptr<X>> v;
public:
A() : v(10) {}
auto get_container() {
return v | transformed( [](std::unique_ptr<X> const& x) -> X const* {return x.get();});
}
};
int main() {
A a;
auto const& v = a.get_container();
a.get_container()[0]->constF();
a.get_container()[0]->nonConst();
return 0;
}
Это должно быть довольно эффективно с оптимизирующим компилятором.
Вы также можете переключиться с std::vector<std::unique_ptr<my_type_t>>
в boost::ptr_vector<my_type_t>
, Он также предполагает владение элементами, хранящимися в указателе, но возвращает const_reference
в operator[] const
такие, что объекты не могут быть изменены.
#include <boost/ptr_container/ptr_vector.hpp>
class X {
public:
void nonConst() {}
};
class A{
boost::ptr_vector<X> v;
public:
boost::ptr_vector<X> const& get_container() const {
return v;
}
};
int main() {
A a;
auto const& v = a.get_container();
a.get_container()[0].nonConst();
return 0;
}
Это защитит элементы от изменения, когда get_container()
возвращает константную ссылку:
prog.cc:26:1: ошибка: 'this' аргумент функции-члена 'nonConst' имеет тип 'const boost::ptr_container_detail::reversible_ptr_container > >, boost::heap_clone_allocator>::Ty_' (также известный как const X'), но функция не помечена const a.get_container()[0].nonConst(); ^~~~~~~~~~~~~~~~~~~~ prog.cc:9:9: примечание: объявленное здесь "nonConst" void nonConst() {} ^ 1 сгенерирована ошибка.