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*();
}
Есть ли веская причина, почему стандарт не предусматривает такого рода вещи?
Я подозреваю, что реальный ответ прост, и тот же самый для многих «Почему C ++ не такой?» вопросы:
Никто не предложил это.
std::vector
а также std::unique_ptr
не разрабатываются одними и теми же людьми в одно и то же время и не используются одинаково, поэтому необязательно следуйте одним и тем же принципам проектирования.
unique_ptr
был специально разработан как легкий класс указателей с обнаружением нулевого состояния (например, указано в необязательный в Предложение добавить служебный класс для представления необязательных объектов (Редакция 3))
Тем не менее, возможность, которую вы спрашиваете, уже на месте, так как оператор * документация гласит:
// 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;
}
}
Для полноты картины я опубликую две дополнительные альтернативы / обходные пути:
Проверка указателя на unique_ptr
с помощью operator bool
#include <iostream>
#include <memory>
int main()
{
std::unique_ptr<int> ptr(new int(42));
if (ptr) std::cout << "before reset, ptr is: " << *ptr << '\n';
ptr.reset();
if (ptr) std::cout << "after reset, ptr is: " << *ptr << '\n';
}
(Это, вероятно, самый клановый способ решить проблему)
Альтернативное решение, хотя и более сложное, должен использовать тип оболочки, который заботится об обработке исключений
Я не могу сказать, почему комитет решил не добавлять безопасный метод разыменования — ответ, вероятно, «потому что это не было предложено» или же «потому что у необработанного указателя его тоже нет». Но тривиально написать собственный бесплатный шаблон функции, который принимает любой указатель в качестве аргумента, сравнивает его с nullptr, а затем либо выдает исключение, либо возвращает ссылку на указанный объект.
Если вы не удалите его с помощью указателя на базовый класс, должна быть возможность публичного вывода из unique_ptr
и просто добавьте такую функцию-член.
Имейте в виду, однако, что использование такого проверенного метода везде может привести к значительному снижению производительности (так же, как и в). Обычно вы хотите проверить свои параметры не более одного раза, для чего лучше подходит отдельный оператор if в начале.
Существует также школа, которая говорит, что вы не должны бросать исключения в ответ на ошибки программирования. Может быть, люди, отвечающие за дизайн unique_ptr
принадлежал этой школе, в то время как люди, разрабатывающие вектор (который намного старше), этого не делали.
Одной из основных целей разработки API интеллектуальных указателей является замена с добавленной стоимостью, без дополнительных ошибок и побочных эффектов, а также с минимальными накладными расходами. if (ptr) ptr->...
как обычно осуществляется безопасный доступ к голому указателю, тот же синтаксис прекрасно работает с умными указателями, поэтому не требуется никаких изменений кода при замене одного на другой.
Дополнительная проверка на достоверность (скажем, на выдачу исключения), помещенная в указатель, будет мешать предсказателю перехода и, таким образом, может оказывать влияние на производительность, что больше не может рассматриваться как замена с нулевой стоимостью.
У вас есть
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 — это простая оболочка для необработанного указателя, нет необходимости создавать исключение, когда вы можете просто проверить логическое условие.
Редактировать:
По-видимому оператор * могу бросить.
Исключения
1) может бросить, например, если указатель определяет оператор броска *
Может быть, кто-то может пролить свет на горячую, чтобы определить оператора броска *
Следуя предложению 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;
}