#c #std
#c #ЗППП
Вопрос:
Я пытаюсь создать not-null unique_ptr
.
template <typename T>
class unique_ref {
public:
template <class... Types>
unique_ref(Typesamp;amp;... Args) { mPtr = std::make_unique<T, Types...>(std::forward<Types>(Args)...); }
T* release() amp;amp; { return mPtr.release(); }
T* release() amp; = delete;
private:
std::unique_ptr<T> mPtr;
};
Моя цель — разрешить release()
только в том случае, если unique_ref
это временно.
Проблема в том, что кто-то может использовать std::move()
это, чтобы «обойти» это:
unique_ref<int> p;
int* p2 = std::move(p).release();
Есть ли способ предотвратить это от move
‘d?
Комментарии:
1. Я думаю, вы слишком беспокоитесь. Если вызывающий вызывал
std::move
, вы должны предположить, что он знает, что делает.2. Приведенный выше код @SidS ни в коем случае не вызывает конструктор перемещения, поэтому мне кажется, что это ничего не даст !?
3. @MichaelKenzel std::move приведет p к ссылке на r-значение, что приведет к выбору перегрузки r-значения release.
4. @MichaelKenzel, меня обманул заголовок — он хочет предотвратить
move
указатель на объект, а не на объект.5. из интереса, если вы не хотите, чтобы пользователь вызывал release(), зачем его предоставлять? Каков реальный вариант использования здесь?
Ответ №1:
Нет никакого способа отличить prvalues (временные значения) от xvalues (результат std::move
) в том, что касается разрешения перегрузки.
И нет никакого способа std::move
предотвратить преобразование lvalue в xvalue.
release
это не операция, которая может поддерживаться «уникальным указателем» с ненулевой гарантией. И ни то, ни другое не является построением / назначением перемещения. Насколько я могу судить, единственный способ обеспечить гарантию — сделать указатель неподвижным и заставить операцию копирования выделить глубокую копию.
Ответ №2:
Вам придется оставить std::move
дело в покое. Когда пользователь вызывает std::move
, он дает сильный сигнал о том, что он точно знает, что делает.
Вы можете защитить себя, хотя во время отладки.
Например, я бы подумал о том, чтобы начать определение класса примерно так:
#include <memory>
#include <cassert>
template <typename T>
class unique_ref {
public:
// a number of problems here, but that is a discussuion for another day
template <class... Types>
unique_ref(Typesamp;amp;... Args)
: mPtr(std::make_unique<T>(std::forward<Types>(Args)...))
{ }
// unique_ref is implicitly move-only
// see check below
bool has_value() const {
return bool(mPtr);
}
// here I am implicitly propagating the container's constness to the
// inner reference yielded. You may not want to do that.
// note that all these accessors are marshalled through one static function
// template. This gives me control of behaviour in exactly one place.
// (DRY principles)
auto operator*() -> decltype(auto) {
return *get_ptr(this);
}
auto operator*() const -> decltype(auto) {
return *get_ptr(this);
}
auto operator->() -> decltype(auto) {
return get_ptr(this);
}
auto operator->() const -> decltype(auto) {
return get_ptr(this);
}
private:
using implementation_type = std::unique_ptr<T>;
implementation_type release() { return std::move(mPtr); }
// this function is deducing constness of the container and propagating it
// that may not be what you want.
template<class MaybeConst>
static auto get_ptr(MaybeConst* self) -> decltype(auto)
{
auto ptr = self->mPtr.get();
assert(ptr);
using self_type = std::remove_pointer_t<decltype(self)>;
if constexpr (std::is_const<self_type>())
return static_cast<T const*>(ptr);
else
return ptr;
}
private:
implementation_type mPtr;
};
struct foo
{
};
auto generate()->unique_ref<foo> {
return unique_ref<foo>();
}
void test()
{
auto rfoo1 = generate();
auto rfoo2 = generate();
// auto rfoo3 = rfoo1; not copyable
// we have to assume that a user knows what he's doing here
auto rfoo3 = std::move(rfoo1);
// but we can add a check
assert(!rfoo1.has_value());
autoamp; a = *rfoo3;
static_assert(!std::is_const<std::remove_reference_t<decltype(a)>>());
const auto rfoo4 = std::move(rfoo3);
autoamp; b = *rfoo4;
static_assert(std::is_const<std::remove_reference_t<decltype(b)>>());
}