Подтвердить что ты не робот

Почему нет уникальной альтернативы unique_ptr:: operator *()?

std::vector имеет функцию-член at() как безопасную альтернативу operator[], поэтому применяется связанная проверка и не создаются ничтожные ссылки:

void foo(std::vector<int> const&x)
{
  const auto&a=x[0];     // What if x.empty()? Undefined behavior!
  const auto&a=x.at(0);  // Throws exception if x.empty().
}

Однако std::unique_ptr не хватает соответствующей функциональности:

void foo(std::unique_ptr<int> const&x)
{
  const auto&a=*x;       // What if bool(x)==false? Undefined behavior!
}

Было бы здорово, если бы std::unique_ptr имела такую ​​безопасную альтернативу, скажем, член ref()cref()), который никогда не возвращает оборванную ссылку, а скорее генерирует исключение. Возможная реализация:

template<typename T>
typename add_lvalue_reference<T>::type
unique_ptr<T>::ref() const noexcept(false)
{
  if(bool(*this)==false)
    throw run_time_error("trying to de-refrence null unique_ptr");
  return this->operator*();
}

Есть ли веская причина, почему стандарт не предоставляет такого рода вещи?

4b9b3361

Ответ 1

Я подозреваю, что реальный ответ прост, и тот же для многих "Почему это не С++, как это?" вопросы:

Никто не предложил его.

std::vector и std::unique_ptr не разрабатываются одними и теми же людьми одновременно и не используются одинаково, поэтому не обязательно должны следовать тем же принципам дизайна.

Ответ 2

unique_ptr был специально разработан как класс легкого указателя с обнаружением нулевого состояния (например, указано в optional в Предложение добавить класс утилиты для представления необязательных объектов (версия 3))

Тем не менее, способность, о которой вы просите, уже на месте, поскольку operator* документация гласит:

// may throw, e.g. if pointer defines a throwing operator*
typename std::add_lvalue_reference<T>::type operator*() const;

Тип pointer определяется как

std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*

Таким образом, благодаря вашему пользовательскому удалению вы можете выполнять любую операцию "на лету", включая проверку нулевого указателя и бросание исключений

#include <iostream>
#include <memory>

struct Foo { // object to manage
    Foo() { std::cout << "Foo ctor\n"; }
    Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }
    Foo(Foo&&) { std::cout << "Foo move ctor\n"; }
    ~Foo() { std::cout << "~Foo dtor\n"; }
};

struct Exception {};

struct InternalPtr {
    Foo *ptr = nullptr;
    InternalPtr(Foo *p) : ptr(p) {}
    InternalPtr() = default;

    Foo& operator*() const {
        std::cout << "Checking for a null pointer.." << std::endl;
        if(ptr == nullptr)
            throw Exception();
        return *ptr;
    }

    bool operator != (Foo *p) {
        if(p != ptr)
            return false;
        else
            return true;
    }
    void cleanup() {
      if(ptr != nullptr)
        delete ptr;
    }
};

struct D { // deleter
    using pointer = InternalPtr;
    D() {};
    D(const D&) { std::cout << "D copy ctor\n"; }
    D(D&) { std::cout << "D non-const copy ctor\n";}
    D(D&&) { std::cout << "D move ctor \n"; }
    void operator()(InternalPtr& p) const {
        std::cout << "D is deleting a Foo\n";
        p.cleanup();
    };
};

int main()
{
    std::unique_ptr<Foo, D> up(nullptr, D()); // deleter is moved

    try {
      auto& e = *up;      
    } catch(Exception&) {
        std::cout << "null pointer exception detected" << std::endl;
    }

}

Живой пример

Для полноты я отправлю две дополнительные альтернативы/обходные пути:

Ответ 3

Я не могу сказать, почему комитет решил не добавлять безопасный метод перегруппировки - ответ, вероятно, "потому что он не был предложен" или "потому что у необработанного указателя нет ни одного". Но тривиально написать шаблон свободной функции самостоятельно, который принимает любой указатель в качестве аргумента, сравнивает его с nullptr, а затем либо выдает исключение, либо возвращает ссылку на объект, указывающий на объект.

Если вы не удаляете его с помощью указателя на базовый класс, его также можно получить публично из unique_ptr и просто добавить такую ​​функцию-член.

Имейте в виду, однако, что использование такого проверенного метода во всем мире может привести к существенному результату (так же, как и на). Обычно вы хотите проверить свои параметры не более одного раза, для которых синтаксис if в начале намного лучше подходит.

Существует также школа, в которой говорится, что вы не должны бросать исключения в ответ на ошибки программирования. Возможно, руководители этой школы занимались исследователями unique_ptr, в то время как люди, проектирующие вектор (который намного старше), не сделали.

Ответ 4

Одной из основных целей дизайна API-интерфейса интеллектуального указателя является замена с добавленной стоимостью, отсутствие gotchas или побочных эффектов и близость к нулю. if (ptr) ptr->... заключается в том, как обычно делается безопасный доступ к голой указателю, тот же синтаксис хорошо работает с интеллектуальными указателями, поэтому для замены кода при замене другого нет необходимости.

Дополнительная проверка на достоверность (например, выброс исключения), помещенная внутри указателя, будет мешать предсказателю ветвления и, таким образом, может иметь эффект постукивания на производительность, что не может считаться заменой нулевой стоимости больше.

Ответ 5

У вас есть

operator bool()

Пример из: cplusplusreference

// example of unique_ptr::operator bool
#include <iostream>
#include <memory>


int main () {
  std::unique_ptr<int> foo;
  std::unique_ptr<int> bar (new int(12));

  if (foo) std::cout << "foo points to " << *foo << '\n';
  else std::cout << "foo is empty\n";

  if (bar) std::cout << "bar points to " << *bar << '\n';
  else std::cout << "bar is empty\n";

  return 0;
}

unique_ptr - простая оболочка для необработанного указателя, не нужно бросать исключение, когда вы можете просто легко проверить логическое состояние.

Изменить: Очевидно, operator* может бросать.

Исключения 1) может бросать, например. если указатель определяет оператор метания *

Возможно, кто-то может пролить некоторые огни на горячие, чтобы определить оператора метания *

Ответ 6

Следуя предложению MikeMB, здесь возможна реализация свободной функции для указателей разыменования и unique_ptr.

template<typename T>
inline T& dereference(T* ptr) noexcept(false)
{
  if(!ptr) throw std::runtime_error("attempt to dereference a nullptr");
  return *ptr;
}

template<typename T>
inline T& dereference(std::unique_ptr<T> const& ptr) noexcept(false)
{
  if(!ptr) throw std::runtime_error("attempt to dereference an empty unique_ptr)");
  return *ptr;
}