A maioria das respostas aqui não aborda a ambiguidade inerente ao ter um ponteiro bruto em uma assinatura de função, em termos de expressar intenção. Os problemas são os seguintes:
O chamador não sabe se o ponteiro aponta para um único objeto ou para o início de uma "matriz" de objetos.
O chamador não sabe se o ponteiro "possui" a memória para a qual aponta. IE, se a função deve ou não liberar a memória. ( foo(new int)
- Isso é um vazamento de memória?).
O chamador não sabe se nullptr
pode ou não ser passado com segurança para a função.
Todos esses problemas são resolvidos por referências:
As referências sempre se referem a um único objeto.
As referências nunca possuem a memória a que se referem, são apenas uma visão da memória.
As referências não podem ser nulas.
Isso faz das referências um candidato muito melhor para uso geral. No entanto, as referências não são perfeitas - há alguns problemas importantes a serem considerados.
- Sem indireto explícito. Isso não é um problema com um ponteiro bruto, pois precisamos usar o
&
operador para mostrar que realmente estamos passando um ponteiro. Por exemplo, int a = 5; foo(a);
não está claro aqui que a está sendo passado por referência e pode ser modificado.
- Anulabilidade. Essa fraqueza dos ponteiros também pode ser uma força, quando realmente queremos que nossas referências sejam anuláveis. Visto que
std::optional<T&>
não é válido (por boas razões), os ponteiros nos dão a nulidade que você deseja.
Então, parece que quando queremos uma referência nula com indireção explícita, devemos buscar um T*
direito? Errado!
Abstrações
Em nosso desespero pela nulidade, podemos buscar T*
e simplesmente ignorar todas as deficiências e ambigüidades semânticas listadas anteriormente. Em vez disso, devemos buscar o que o C ++ faz melhor: uma abstração. Se simplesmente escrevermos uma classe que envolve um ponteiro, obteremos a expressividade, bem como a nulidade e a indireta explícita.
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;
};
Essa é a interface mais simples que eu poderia criar, mas faz o trabalho com eficiência. Permite inicializar a referência, verificando se existe um valor e acessando o valor. Podemos usá-lo assim:
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
Alcançamos nossos objetivos! Vamos agora ver os benefícios, em comparação com o ponteiro bruto.
- A interface mostra claramente que a referência deve se referir apenas a um objeto.
- Claramente, ele não possui a memória a que se refere, pois não possui destruidor definido pelo usuário e não possui método para excluir a memória.
- O chamador sabe que
nullptr
pode ser passado, já que o autor da função está solicitando explicitamente umoptional_ref
Poderíamos tornar a interface mais complexa a partir daqui, como adicionar operadores de igualdade, uma interface monádica get_or
e map
um método que obtém o valor ou gera uma exceção, constexpr
suporte. Isso pode ser feito por você.
Em conclusão, em vez de usar ponteiros brutos, raciocine sobre o que esses ponteiros realmente significam em seu código e aproveite uma abstração de biblioteca padrão ou escreva sua própria. Isso melhorará seu código significativamente.
new
de criar um ponteiro e os problemas resultantes de propriedade.