c++ - after - null object pattern c#
¿Devolviendo una "referencia NULL" en C++? (7)
¿Por qué "además de usar punteros"? Usar punteros es la forma en que lo haces en C ++. A menos que defina algún tipo "opcional" que tenga algo así como la función isNull()
que mencionó. (o use uno existente, como boost::optional
)
Las referencias están diseñadas y garantizadas para que nunca sean nulas . Preguntar "¿y cómo los hago nulos?" Es absurdo. Utiliza punteros cuando necesita una "referencia anulable".
En lenguajes de tipado dinámico como JavaScript o PHP, a menudo hago funciones como:
function getSomething(name) {
if (content_[name]) return content_[name];
return null; // doesn''t exist
}
Devuelvo un objeto si existe o null
si no.
¿Cuál sería el equivalente en C ++ usando referencias? ¿Hay algún patrón recomendado en general? Vi algunos frameworks que tienen un método isNull()
para este propósito:
SomeResource SomeClass::getSomething(std::string name) {
if (content_.find(name) != content_.end()) return content_[name];
SomeResource output; // Create a "null" resource
return output;
}
Entonces la persona que llama verificaría el recurso de esa manera:
SomeResource r = obj.getSomething("something");
if (!r.isNull()) {
// OK
} else {
// NOT OK
}
Sin embargo, tener que implementar este tipo de método mágico para cada clase parece pesado. Tampoco parece obvio cuando el estado interno del objeto se debe establecer de "nulo" a "no nulo".
¿Hay alguna alternativa a este patrón? Ya sé que se puede hacer usando punteros, pero me pregunto cómo y si se puede hacer con referencias. ¿O debería renunciar a devolver objetos "nulos" en C ++ y usar algún patrón específico de C ++? Cualquier sugerencia sobre la forma correcta de hacerlo sería apreciada.
Aquí hay un par de ideas:
Alternativa 1:
class Nullable
{
private:
bool m_bIsNull;
protected:
Nullable(bool bIsNull) : m_bIsNull(bIsNull) {}
void setNull(bool bIsNull) { m_bIsNull = bIsNull; }
public:
bool isNull();
};
class SomeResource : public Nullable
{
public:
SomeResource() : Nullable(true) {}
SomeResource(...) : Nullable(false) { ... }
...
};
Alternativa 2:
template<class T>
struct Nullable<T>
{
Nullable(const T& value_) : value(value_), isNull(false) {}
Nullable() : isNull(true) {}
T value;
bool isNull;
};
Este código a continuación demuestra cómo devolver referencias "inválidas"; es solo una forma diferente de usar punteros (el método convencional).
No se recomienda que use esto en el código que otros usarán, ya que se espera que las funciones que devuelven referencias siempre devuelvan referencias válidas.
#include <iostream>
#include <cstddef>
#define Nothing(Type) *(Type*)nullptr
//#define Nothing(Type) *(Type*)0
struct A { int i; };
struct B
{
A a[5];
B() { for (int i=0;i<5;i++) a[i].i=i+1; }
A& GetA(int n)
{
if ((n>=0)&&(n<5)) return a[n];
else return Nothing(A);
}
};
int main()
{
B b;
for (int i=3;i<7;i++)
{
A &ra=b.GetA(i);
if (!&ra) std::cout << i << ": ra=nothing/n";
else std::cout << i << ": ra=" << ra.i << "/n";
}
return 0;
}
La macro Nothing(Type)
devuelve un valor , en este caso representado por nullptr
; también puede usar 0
, a la que se le asigna la dirección de la referencia. Esta dirección ahora se puede verificar como si hubiera estado utilizando punteros.
No puede hacer esto durante las referencias, ya que nunca deberían ser NULL. Básicamente, hay tres opciones, una con un puntero y otras con semántica de valores.
Con un puntero (nota: esto requiere que el recurso no se destruya mientras la persona que llama tiene un puntero al mismo; también asegúrese de que la persona que llama sabe que no necesita eliminar el objeto):
SomeResource* SomeClass::getSomething(std::string name) { std::map<std::string, SomeResource>::iterator it = content_.find(name); if (it != content_.end()) return &(*it); return NULL; }
Usar
std::pair
con unbool
para indicar si el elemento es válido o no (nota: requiere que SomeResource tenga un constructor predeterminado apropiado y no sea costoso de construir):std::pair<SomeResource, bool> SomeClass::getSomething(std::string name) { std::map<std::string, SomeResource>::iterator it = content_.find(name); if (it != content_.end()) return std::make_pair(*it, true); return std::make_pair(SomeResource(), false); }
Usando
boost::optional
:boost::optional<SomeResource> SomeClass::getSomething(std::string name) { std::map<std::string, SomeResource>::iterator it = content_.find(name); if (it != content_.end()) return *it; return boost::optional<SomeResource>(); }
Si quieres una semántica de valores y tienes la capacidad de usar Boost, te recomendaría la opción tres. La principal ventaja de boost::optional
sobre std::pair
es que un valor boost::optional
unitializado boost::optional
no construye el tipo que encapsula. Esto significa que funciona para tipos que no tienen un constructor predeterminado y ahorra tiempo / memoria para los tipos con un constructor predeterminado no trivial.
También modifiqué tu ejemplo para que no estés buscando el mapa dos veces (reutilizando el iterador).
Puedo pensar en algunas formas de manejar esto:
- Como otros sugirieron, use
boost::optional
- Haga que el objeto tenga un estado que indique que no es válido (¡Yuk!)
- Use puntero en lugar de referencia
- Tener una instancia especial de la clase que es el objeto nulo
- Lanzar una excepción para indicar el error (no siempre aplicable)
Un enfoque agradable y relativamente no intrusivo, que evita el problema si se implementan métodos especiales para todos los tipos, es el utilizado con boost::optional . Es esencialmente un contenedor de plantilla que le permite verificar si el valor mantenido es "válido" o no.
Por cierto, creo que esto está bien explicado en los documentos, pero ten cuidado con boost::optional
of bool
, esta es una construcción que es difícil de interpretar.
Editar : la pregunta pregunta sobre "Referencia NULA", pero el fragmento de código tiene una función que devuelve por valor. Si esa función de hecho devolvió una referencia:
const someResource& getSomething(const std::string& name) const ; // and possibly non-const version
entonces la función solo tendría sentido si someResource
las someResource
se hace referencia tiene una vida al menos tan larga como la del objeto que devuelve la referencia (de lo contrario, podría tener una referencia colgante). En este caso, parece perfectamente bien devolver un puntero:
const someResource* getSomething(const std::string& name) const; // and possibly non-const version
pero debe dejar absolutamente en claro que quien llama no se apropia del puntero y no debe intentar eliminarlo.
a diferencia de Java y C # en C ++, el objeto de referencia no puede ser nulo.
entonces aconsejaría 2 métodos que uso en este caso.
1 - en lugar de referencia use un tipo que tenga nulo, como std :: shared_ptr
2 - obtenga la referencia como un parámetro de salida y devuelva Boolean para tener éxito.
bool SomeClass::getSomething(std::string name, SomeResource& outParam) {
if (content_.find(name) != content_.end())
{
outParam = content_[name];
return true;
}
return false;
}