Запретить std::перемещение по объекту?

#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)>>());
}