2010-12-13 22 views
8

que tiene una estructura de C++ y un método:método de C++ que puede/no puede devolver una estructura

struct Account 
{ 
    unsigned int id; 
    string username; 
    ... 
}; 


Account GetAccountById(unsigned int id) const { } 

que puede devolver una estructura cuenta si existe la cuenta, pero qué hacer si no hay cuenta?

Me idea de tener:

  • Una "es válida" bandera en la estructura (por lo que una vacía puede ser devuelto, con ese conjunto en false)
  • Un adicional "es válida" puntero (const cadena & id, int * is_ok) que se establece si la salida es válida
  • Devolviendo una Cuenta * en su lugar, y devolviendo un puntero a una estructura, o NULO si no existe?

¿Existe alguna forma de hacerlo?

Respuesta

13

se le olvidó la más obvia, en C++:

bool GetAccountById(unsigned int id, Account& account); 

Volver true y rellene la referencia proporcionada si la cuenta existe, de lo contrario volver false.

También podría ser conveniente utilizar el hecho de que los punteros pueden ser nulos, y que tiene:

bool GetAccountById(unsigned int id, Account* account); 

que podría definirse para volver true si existe el ID de la cuenta, pero sólo (por supuesto) para llenar en la cuenta proporcionada si el puntero no es nulo. A veces es útil poder probar la existencia, y esto ahorra tener que tener un método dedicado solo para ese propósito.

Es una cuestión de gusto lo que prefiera tener.

+1

+1 me gané por unos segundos. :) – casablanca

+3

Prefiero la respuesta de Juraj a continuación, porque codifica la capacidad de nulidad en el tipo, lo que aumenta la seguridad del tipo del código. Especialmente, usando su método, uno puede olvidarse accidentalmente de verificar la devolución, mientras que si el retorno es el valor, entonces tiene que usarlo :) –

+2

Creo que mi principal problema aquí es nombrar la función. GetX() para mí siempre debe devolver X (si no está allí, es una excepción). Si existe la posibilidad de que X no exista, entonces debe haber una función findX() que devuelva un iterador (o un objeto similar al iterador). Si no se puede encontrar el objeto, esto puede determinarse a partir del iterador si se encuentra que el iterador se puede convertir en una referencia del objeto.Pasar los parámetros de salida no es intuitivo y requiere que pueda crear un objeto inválido/vacío (para pasarlo y llenarlo). Los objetos inválidos/vacíos son propensos a errores. –

3

Depende de la probabilidad de que piense que la cuenta no existirá.

Si es realmente excepcional, en las entrañas del sistema bancario donde se supone que los datos son válidos, entonces quizás haga una excepción.

Si está en un nivel de interfaz de usuario, validando los datos, entonces probablemente no arroje una excepción.

La devolución de un puntero significa que alguien tiene que desasignar la memoria asignada, eso es más complicado.

¿Se puede utilizar una 'ID de marcador' (como 0) para indicar 'cuenta no válida'?

7

De las opciones indicadas, devolvería Account*. Pero devolver el puntero puede tener un efecto secundario negativo en la interfaz.

Otra posibilidad es throw, una excepción cuando no existe dicha cuenta. También puedes probar boost::optional.

+7

+1 para 'boost :: opcional' –

0

Otra forma de devolver una referencia es devolver un puntero. Si la cuenta existe, devuelve su puntero. De lo contrario, devuelve NULL.

2

Usaría Account* y agregaría un comentario de documentación al método que establezca que el valor de retorno puede ser NULO.

+1

Pero luego, como dijo Jonathan, alguien tiene que liberar ese puntero que a menudo conduce a pérdidas de memoria. –

+0

A menos que alguien más devuelva el objeto señalado por el puntero devuelto. – Kos

-1

boost :: opcional es probablemente lo mejor que puede hacer en un idioma tan roto que no tiene variantes nativas.

+3

Diría que los idiomas que intentan hacer todo lo que piensa por usted son los que están rotos. –

2

Existen varios métodos.

1) Lanza una excepción. Esto es útil si desea GetAccountById para devolver la cuenta por el valor y el uso de excepciones se ajusta a su modelo de programación. Algunos le dirán que las "excepciones" están "destinadas" a ser utilizadas solo en circunstancias excepcionales. Cosas como "falta de memoria" o "computadora en llamas". Esto es muy discutible, y para cada programador que encuentre que dice que las excepciones no son para control de flujo, encontrará otro (incluido yo) que dice que las excepciones se pueden usar para controlar el flujo. Debes pensar sobre esto y decidir por ti mismo.

Account GetAccountById(unsigned int id) const 
{ 
    if(account_not_found) 
    throw std::runtime_error("account not found"); 
} 

2) No devolver y Account por valor. En su lugar, volver por el puntero (puntero preferentemente inteligente), y NULL volver cuando no encontró la cuenta:

boost::shared_ptr<Account> GetAccountById(unsigned int id) const 
{ 
    if(account_not_found) 
    return NULL; 
} 

3) Devuelve un objeto que tiene una bandera 'presencia' que indica si el elemento de datos es presente. Boost.Optional es un ejemplo de dicho dispositivo, pero en caso de que no pueda usar Boost aquí hay un objeto con plantilla que tiene un miembro bool que es true cuando el elemento de datos está presente, y es false cuando no lo está. El elemento de datos en sí mismo se almacena en el miembro value_. Debe ser predeterminado construible.

template<class Value> 
struct PresenceValue 
{ 
    PresenceValue() : present_(false) {}; 
    PresenceValue(const Value& val) : present_(true), value_(val) {}; 
    PresenceValue(const PresenceValue<Value>& that) : present_(that.present_), value_(that.value_) {}; 
    explicit PresenceValue(Value val) : present_(true), value_(val) {}; 
    template<class Conv> explicit PresenceValue(const Conv& conv) : present_(true), value_(static_cast<Value>(conv)) {}; 
    PresenceValue<Value>& operator=(const PresenceValue<Value>& that) { present_ = that.present_; value_ = that.value_; return * this; } 

    template<class Compare> bool operator==(Compare rhs) const 
    { 
     if(!present_) 
      return false; 
     return rhs == value_; 
    } 
    template<class Compare> bool operator==(const Compare* rhs) const 
    { 
     if(!present_) 
      return false; 
     return rhs == value_; 
    } 
    template<class Compare> bool operator!=(Compare rhs) const { return !operator==(rhs); } 
    template<class Compare> bool operator!=(const Compare* rhs) const { return !operator==(rhs); } 

    bool operator==(const Value& rhs) const { return present_ && value_ == rhs; } 
    operator bool() const { return present_ && static_cast<bool>(value_); } 

    operator Value() const; 

    void Reset() { value_ = Value(); present_ = false; } 

    bool present_; 
    Value value_; 
}; 

Para simplificar, me gustaría crear un typedef para Account:

typedef PresenceValue<Account> p_account; 

... y luego volver esto de su función:

p_account GetAccountByIf(...) 
{ 
    if(account_found) 
    return p_account(the_account); // this will set 'present_' to true and 'value_' to the account 
    else 
    return p_account(); // this will set 'present_' to false 
} 

Usar esto es sencillo:

p_account acct = FindAccountById(some_id); 
if(acct.present_) 
{ 
    // magic happens when you found the account 
} 
+0

2) ¿Quisiste decir 'scoped_ptr' en lugar de' shared_ptr'? IMO, debe ser 'scoped_ptr' o equivalente si queremos devolver una copia, o un puntero sin procesar si devolvemos solo un manejador a una' Cuenta' existente. El 'shared_ptr' solo tendría sentido para mí si la clase tuviera sus' Account's como 'shared_ptr's sin una estricta propiedad, y es más probable que las cuentas sean estrictamente propiedad de alguna entidad. – Kos

+0

3) ¿Eso es equivalente a 'boost :: optional' que a menudo se menciona aquí? – Kos

+0

@Kos: re 2), eh, probablemente. Siempre me equivoco. :) –

0

Hay otra manera similar al patrón "es válido". Estoy desarrollando una aplicación ahora que tiene muchas de esas cosas. Pero mis ID nunca pueden ser menores que 1 (son todos campos SERIE en una base de datos PostgreSQL) así que solo tengo un constructor predeterminado para cada estructura (o clase en mi caso) que inicializa id con -1 y isValid() método que devuelve verdadero si id no es igual a -1. Funciona perfectamente para mi

0

que haría:

class Bank 
{ 
    public: 

    class Account {}; 
    class AccountRef 
    { 
     public: 
      AccountRef():     m_account(NULL) {} 
      AccountRef(Account const& acc) m_account(&acc) {} 
      bool isValid() const        { return m_account != NULL);} 
      Account const& operator*()      { return *m_account; } 
      operator bool()         { return isValid(); } 
     private: 
      Account const* m_account; 
    }; 
    Account const& GetAccountById(unsigned int id) const 
    { 
     if (id < m_accounts.size()) 
     { return m_accounts[id]; 
     } 
     throw std::outofrangeexception("Invalid account ID"); 
    } 

    AccountRef FindAccountById(unsigned int id) const 
    { 
     if (id < m_accounts.size()) 
     { return AccountRef(m_accounts[id]); 
     } 
     return AccountRef(); 
    } 
    private: 
    std::vector<Account> m_accounts; 
}; 

Un método llamado get debería devolver siempre (en mi humilde opinión) el objeto pidió. Si no existe, entonces eso es una excepción. Si existe la posibilidad de que algo no exista, también debe proporcionar un método de búsqueda que pueda determinar si el objeto existe para que el usuario pueda probarlo.

int main() 
{ 
    Bank Chase; 

    // Get a reference 
    // As the bank ultimately ownes the account. 
    // You just want to manipulate it. 
    Account const& account = Chase.getAccountById(1234); 

    // If there is the possibility the account does not exist then use find() 
    AccountRef ref = Chase.FindAccountById(12345); 
    if (!ref) 
    {  // Report error 
      return 1; 
    } 
    Account const& anotherAccount = *ref; 
} 

Ahora podría haber usado un puntero en lugar de dedicarme al esfuerzo de crear AccountRef.El problema es que los punteros no tienen propiedad semántica y, por lo tanto, no hay una indicación verdadera de quién debe poseer (y por lo tanto eliminar) el puntero.

Como resultado, me gusta envolver los punteros en un contenedor que permite al usuario manipular el objeto solo como yo quiero. En este caso, AccountRef no expone el puntero, por lo que el usuario de AccountRef no tiene la oportunidad de intentar y eliminar la cuenta.

Aquí puede verificar si AccountRef es válido y extraer una referencia a una cuenta (suponiendo que sea válida). Como el objeto solo contiene un puntero, es probable que el compilador optimice esto hasta el punto de que no es más caro que pasar el puntero. El beneficio es que el usuario no puede abusar accidentalmente de lo que le he dado.

Resumen: AccountRef no tiene un costo real en tiempo de ejecución. Sin embargo, proporciona seguridad de tipo (ya que oculta el uso del puntero).

0

Me gusta hacer una combinación de lo que sugiere con el indicador Válido y lo que alguien más sugirió con el patrón de objeto nulo.

Tengo una clase base llamada Status que heredé de los objetos que deseo utilizar como valores devueltos. Voy a dejar la mayor parte de esta discusión, ya que es un poco más complicado, pero se ve algo como esto

class Status 
{ 
    public: 
    Status(bool isOK=true) : mIsOK(isOK) 
    operator bool() {return mIsOK;} 
    private 
    bool mIsOK 
}; 

ahora tendría

class Account : public Status 
{ 
    public: 
    Account() : Status(false) 
    Account(/*other parameters to initialize an account*/) : ... 
    ... 
}; 

Ahora bien, si se crea una cuenta sin parámetros:

Account A; 

No válido. Pero si crea una cuenta con datos

Account A(id, name, ...); 

Es válido.

Prueba la validez con el operador bool.

Account A=GetAccountByID(id); 
if (!A) 
{ 
    //whoa there! that's an invalid account! 
} 

Hago esto cuando estoy trabajando con tipos matemáticos. Por ejemplo, yo no quiero tener que escribir una función que tiene este aspecto

bool Matrix_Multiply(a,b,c); 

donde a, b, yc son matrices. Prefiero escribir

c=a*b; 

con sobrecarga del operador. Pero hay casos en los que a y b no se pueden multiplicar, por lo que no siempre es válido. Así que simplemente devuelven una c no válida si no funciona, y puedo hacer

c=a*b; 
if (!c) //handle the problem. 
Cuestiones relacionadas