Большинство ответов здесь не в состоянии устранить неоднозначность, присущую необработанному указателю в сигнатуре функции с точки зрения выражения намерения. Проблемы заключаются в следующем:
Вызывающая сторона не знает, указывает ли указатель на один объект или на начало «массива» объектов.
Вызывающая сторона не знает, «принадлежит» ли указатель памяти, на которую он указывает. IE, должна ли функция освободить память. ( foo(new int)
- Это утечка памяти?).
Вызывающая сторона не знает, nullptr
может ли она быть безопасно передана в функцию.
Все эти проблемы решаются по ссылкам:
Ссылки всегда относятся к одному объекту.
Ссылки никогда не владеют памятью, к которой они относятся, они просто видят память.
Ссылки не могут быть нулевыми.
Это делает ссылки гораздо лучшим кандидатом для общего пользования. Тем не менее, ссылки не являются идеальными - есть несколько основных проблем, которые необходимо учитывать.
- Нет явного косвенного Это не проблема с необработанным указателем, так как мы должны использовать
&
оператор, чтобы показать, что мы действительно передаем указатель. Например, int a = 5; foo(a);
здесь совершенно не ясно, что a передается по ссылке и может быть изменено.
- Допустимость пустой. Эта слабость указателей также может быть сильной стороной, когда мы действительно хотим, чтобы наши ссылки были обнуляемыми. Видя, что
std::optional<T&>
это недопустимо (по уважительным причинам), указатели дают нам такую обнуляемость, которую вы хотите.
Таким образом, кажется, что когда мы хотим пустую ссылку с явным косвенным обращением, мы должны достичь T*
права? Неправильно!
Абстракции
В нашем отчаянии за обнуляемость мы можем достичь T*
и просто игнорировать все недостатки и семантическую двусмысленность, перечисленные ранее. Вместо этого мы должны достичь того, что C ++ делает лучше всего: абстракция. Если мы просто напишем класс, который обтекает указатель, мы получим выразительность, а также обнуляемость и явную косвенность.
template <typename T>
struct optional_ref {
optional_ref() : ptr(nullptr) {}
optional_ref(T* t) : ptr(t) {}
optional_ref(std::nullptr_t) : ptr(nullptr) {}
T& get() const {
return *ptr;
}
explicit operator bool() const {
return bool(ptr);
}
private:
T* ptr;
};
Это самый простой интерфейс, который я мог придумать, но он эффективно выполняет свою работу. Это позволяет инициализировать ссылку, проверять, существует ли значение и получать доступ к значению. Мы можем использовать это так:
void foo(optional_ref<int> x) {
if (x) {
auto y = x.get();
// use y here
}
}
int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability
Мы достигли наших целей! Давайте теперь посмотрим на преимущества по сравнению с необработанным указателем.
- Интерфейс ясно показывает, что ссылка должна ссылаться только на один объект.
- Очевидно, он не владеет памятью, на которую ссылается, поскольку у него нет определенного пользователем деструктора и метода удаления памяти.
- Звонящий знает, что его
nullptr
можно передать, поскольку автор функции явно запрашиваетoptional_ref
Мы могли бы сделать интерфейс более сложным, например, добавив операторы равенства, монадику get_or
и map
интерфейс, метод, который получает значение или выдает исключение, constexpr
поддержку. Это может быть сделано вами.
В заключение, вместо использования сырых указателей, подумайте о том, что эти указатели действительно означают в вашем коде, и либо используйте стандартную абстракцию библиотеки, либо напишите свою собственную. Это значительно улучшит ваш код.
new
создании указателя и возникающих в результате проблем владения.