Devuelve un objeto "NULL" si no se encuentra el resultado de la búsqueda

94

Soy bastante nuevo en C ++, así que tiendo a diseñar con muchos Java-isms mientras aprendo. De todos modos, en Java, si tuviera una clase con un método de 'búsqueda' que devolviera un objeto Tde un Collection< T >parámetro que coincidiera con un parámetro específico, devolvería ese objeto y si el objeto no se encontrara en la colección, volvería null. Luego, en mi función de llamada, solo verificaríaif(tResult != null) { ... }

En C ++, descubro que no puedo devolver un nullvalor si el objeto no existe. Solo quiero devolver un 'indicador' de tipo T que notifica a la función de llamada que no se ha encontrado ningún objeto. No quiero lanzar una excepción porque no es realmente una circunstancia excepcional.

Así es como se ve mi código en este momento:

class Node {
    Attr& getAttribute(const string& attribute_name) const {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            return NULL; // what should this be?
    }

private:
    vector<Attr> attributes;
}

¿Cómo puedo cambiarlo para poder dar ese tipo de marcador?

adúrico
fuente
6
Exception y NULL no siempre son las únicas soluciones. A menudo puede elegir un valor para devolver indicando no encontrado: por ejemplo, std::find(first, last, value)devuelve lastsi ningún elemento coincide.
Cascabel

Respuestas:

70

En C ++, las referencias no pueden ser nulas. Si desea devolver opcionalmente null si no se encuentra nada, debe devolver un puntero, no una referencia:

Attr *getAttribute(const string& attribute_name) const {
   //search collection
   //if found at i
        return &attributes[i];
   //if not found
        return nullptr;
}

De lo contrario, si insiste en regresar por referencia, debe lanzar una excepción si no se encuentra el atributo.

(Por cierto, me preocupa un poco que su método sea consty devuelva un no constatributo. Por razones filosóficas, sugeriría regresar const Attr *. Si también desea modificar este atributo, puede sobrecargar con un no constmétodo devolviendo un no constatributo también.)

Jesse Beder
fuente
2
Gracias. Por cierto, ¿es esta una forma aceptada de diseñar esa rutina?
Aduric
6
@aduric: Sí. Las referencias implican que el resultado tiene que existir. Los punteros implican que el resultado podría no existir.
Proyecto de ley
7
Solo por curiosidad, ¿volveremos en nullptrlugar de NULLpor c ++ 11 ahora?
Espectral
1
sí, siempre use nullptr sobre NULL en C ++ 11 y posteriores. si necesita ser compatible con versiones anteriores, entonces no lo haga
Conrad Jones
56

Aquí hay varias respuestas posibles. Quiere devolver algo que pueda existir. Aquí hay algunas opciones, que van desde la menos preferida a la más preferida:

  • Devuelve por referencia y la señal no se puede encontrar por excepción.

    Attr& getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            throw no_such_attribute_error;
    }

Es probable que no encontrar atributos sea una parte normal de la ejecución y, por lo tanto, no sea muy excepcional. El manejo de esto sería ruidoso. No se puede devolver un valor nulo porque es un comportamiento indefinido tener referencias nulas.

  • Regresar por puntero

    Attr* getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return &attributes[i];
       //if not found
            return nullptr;
    }

Es fácil olvidarse de verificar si un resultado de getAttribute sería un puntero no NULL y es una fuente fácil de errores.

  • Utilice Boost.Optional

    boost::optional<Attr&> getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            return boost::optional<Attr&>();
    }

Un impulso :: opcional significa exactamente lo que está sucediendo aquí y tiene métodos fáciles para inspeccionar si se encontró tal atributo.


Nota al margen: std :: Optional se votó recientemente en C ++ 17, por lo que esto será algo "estándar" en un futuro próximo.

Dragón Kaz
fuente
+1 Solo mencionaría boost :: opcional primero, y solo mencionaría brevemente las otras alternativas.
Nemanja Trifunovic
Ya vi boost :: optional mencionado en alguna parte, pero estaba pensando que requería demasiada sobrecarga. Si usarlo es el mejor enfoque para este tipo de problemas, comenzaré a usarlo.
aduric
boost::optionalno implica mucha sobrecarga (sin asignación dinámica), por lo que es tan bueno. Usarlo con valores polimórficos requiere ajustar referencias o punteros.
Matthieu M.
2
@MatthieuM. Es probable que el aduric general al que se refería no fuera el rendimiento, sino el costo de incluir una biblioteca externa en el proyecto.
Swoogan
Un apéndice a mi respuesta: tenga en cuenta que hay un movimiento en marcha para estandarizar lo opcional como un componente estándar, probablemente para lo que bien podría ser C ++ 17. Por eso vale la pena conocer esta técnica.
Kaz Dragon
22

Puede crear fácilmente un objeto estático que represente un retorno NULO.

class Attr;
extern Attr AttrNull;

class Node { 
.... 

Attr& getAttribute(const string& attribute_name) const { 
   //search collection 
   //if found at i 
        return attributes[i]; 
   //if not found 
        return AttrNull; 
} 

bool IsNull(const Attr& test) const {
    return &test == &AttrNull;
}

 private: 
   vector<Attr> attributes; 
};

Y en algún lugar de un archivo fuente:

static Attr AttrNull;
Mark Ransom
fuente
¿No debería NodeNull ser de tipo Attr?
aduric
2

Como ha descubierto, no puede hacerlo de la forma en que lo ha hecho en Java (o C #). Aquí hay otra sugerencia, puede pasar la referencia del objeto como un argumento y devolver un valor bool. Si el resultado se encuentra en su colección, puede asignarlo a la referencia que se está pasando y devolver 'verdadero'; de lo contrario, devolver 'falso'. Considere este código.

typedef std::map<string, Operator> OPERATORS_MAP;

bool OperatorList::tryGetOperator(string token, Operator& op)
{
    bool val = false;

    OPERATORS_MAP::iterator it = m_operators.find(token);
    if (it != m_operators.end())
    {
        op = it->second;
        val = true;
    }
    return val;
}

La función anterior tiene que encontrar el Operador contra la clave 'token', si encuentra la que devuelve verdadero y asigna el valor al parámetro Operador & op.

El código de la persona que llama para esta rutina se ve así

Operator opr;
if (OperatorList::tryGetOperator(strOperator, opr))
{
    //Do something here if true is returned.
}
AB
fuente
1

La razón por la que no puede devolver NULL aquí es porque ha declarado su tipo de retorno como Attr&. El final &hace que el valor de retorno sea una "referencia", que es básicamente un puntero garantizado de no ser nulo a un objeto existente. Si desea poder devolver un valor nulo, cambie Attr&a Attr*.

JSB ձոգչ
fuente
0

No puede regresar NULLporque el tipo de retorno de la función es un objeto referencey no un pointer.

codaddict
fuente
-3

Puedes probar esto:

return &Type();
Creado
fuente
6
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo a la pregunta para los lectores en el futuro, y es posible que esas personas no conozcan los motivos de su sugerencia de código.
NathanOliver
Esto probablemente devuelva una referencia muerta a un objeto en la pila de métodos, ¿no es así?
mpromonet