Al devolver la estructura de una función, ¿cómo puedo verificar que se haya inicializado?

StackOverflow https://stackoverflow.com/questions/1017185

  •  06-07-2019
  •  | 
  •  

Pregunta

Tengo la siguiente estructura en C ++:

struct routing_entry {
        unsigned long destSeq;  // 32 bits
        unsigned long nextHop   // 32 bits
        unsigned char hopCount; // 8 bits
}; 

Y tengo la siguiente función:

routing_entry Cnode_router_aodv::consultTable(unsigned int destinationID ) {    
    routing_entry route;

    if ( routing_table.find(destinationID) != routing_table.end() )
        route = routing_table[destinationID];

    return route; // will be "empty" if not found
}

" routing_table " es un stl :: mapa definido de la siguiente manera:

map< unsigned long int, routing_entry > routing_table;

Mi pregunta ahora es, cuando uso la función consultTable, quiero verificar que el valor de retorno esté realmente inicializado, de alguna manera como en el pseudocódigo de Java (porque vengo del campamento de Java):

Route consultTable(int id) {
    Route r = table.find(id);
    return r;
}

luego verificando si r == nulo

¿Fue útil?

Solución

Hay algunos problemas aquí. Lo más urgente puede ser lo que sucede cuando no se encuentra la ID de destino. Como no tiene un constructor en routing_entry y no está inicializando por defecto, tendrá valores indefinidos.

// the data inside route is undefined at this point
routing_entry route;

Una forma de manejar esto es inicializar por defecto. Esto funciona al indicar al compilador que llene la estructura con ceros. Esto es una especie de truco prestado de C pero funciona bien aquí.

routing_entry route={0};

Usted menciona que proviene de Java, a diferencia de Java, los miembros de estructura y clase no están inicializados en 0, por lo que realmente debería manejar eso de alguna manera. Otra forma es definir un constructor:

struct routing_entry
{
  routing_entry()
  : destSeq(0)
  , nextHop(0)
  , hopCount(0)
  { }

            unsigned long destSeq;  // 32 bits
            unsigned long nextHop;   // 32 bits
            unsigned char hopCount; // 8 bits
};

También tenga en cuenta que en C ++, el tamaño de los miembros enteros y char no está definido en bits. El tipo char es 1 byte (pero un byte no está definido, pero generalmente es de 8 bits). Los largos suelen ser de 4 bytes en estos días, pero pueden tener algún otro valor.

Pasando a su consultTable , con la inicialización fijada:

routing_entry Cnode_router_aodv::consultTable(unsigned int destinationID )
{    
  routing_entry route={0};

  if ( routing_table.find(destinationID) != routing_table.end() )
        route = routing_table[destinationID];

  return route; // will be "empty" if not found
}

Una forma de saberlo podría ser verificar si la estructura todavía está puesta a cero. Prefiero refactorizar para que la función devuelva bool para indicar éxito. Además, siempre escribo las estructuras STL definidas por simplicidad, así que lo haré aquí:

typedef map< unsigned long int, routing_entry > RoutingTable;
RoutingTable routing_table;

Luego pasamos una referencia a la entrada de enrutamiento para completar. Esto puede ser más eficiente para el compilador, pero probablemente eso sea irrelevante aquí; de todos modos, este es solo un método.

bool Cnode_router_aodv::consultTable(unsigned int destinationID, routing_entry &entry)
{
  RoutingTable::const_iterator iter=routing_table.find(destinationID);
  if (iter==routing_table.end())
    return false;
  entry=iter->second;
  return true;
}

Lo llamarías así:

routing_entry entry={0};
if (consultTable(id, entry))
{
  // do something with entry
}

Otros consejos

La mejor manera que he encontrado para esto es usar boost :: opcional , que está diseñado para resolver exactamente este problema.

Su función se vería así: -

boost::optional<routing_entry> consultTable(unsigned int destinationID )
{    
  if ( routing_table.find(destinationID) != routing_table.end() )
    return routing_table[destinationID];
  else
    return boost::optional<routing_entry>()
}

Y su código de llamada se ve como

boost::optional<routing_entry> route = consultTable(42);
if (route)
  doSomethingWith(route.get())   
else
  report("consultTable failed to locate 42");

Generalmente, el uso de '' fuera '' los parámetros (pasar un puntero - o referencia - a un objeto que luego se `` completa '' con la función llamada está mal visto en C ++. El enfoque de que todo `` devuelto '' por una función está contenido en el valor de retorno, y que ninguna modificación de los parámetros de la función puede hacer que el código sea más legible y mantenible a largo plazo.

Esta es la solución típica para su problema:

bool Cnode_router_aodv::consultTable(unsigned int destinationID, 
                                     routing_entry* route ) {    
  if ( routing_table.find(destinationID) != routing_table.end() ) {
    *route = routing_table[destinationID];
    return true;
  }
  return false;
}

En lugar de un puntero, podría usar una referencia; es una cuestión de estilo.

Primera nota que en C ++, a diferencia de Java, los usuarios pueden definir tipos de valores. Esto significa que hay 2 ^ 32 * 2 ^ 32 * 2 ^ 8 valores posibles para una ruta_enrutamiento. Si lo desea, puede pensar en routing_entry como un tipo primitivo de 72 bits, aunque debe tener un poco de cuidado con la analogía.

Entonces, en Java route puede ser nulo, y hay 2 ^ 32 * 2 ^ 32 * 2 ^ 8 + 1 valores útiles diferentes para una variable routing_entry . En C ++, no puede ser nulo. En Java " vacío " podría significar devolver una referencia nula. En C ++, solo los punteros pueden ser nulos, y routing_entry no es un tipo de puntero. Entonces, en su código en este caso " vacío " significa "no tengo idea de qué valor tiene esta cosa, porque nunca la inicialicé ni le asigné".

En Java, un objeto routing_entry se asignaría en el montón. En C ++ no desea hacerlo a menos que sea necesario, porque la administración de memoria en C ++ requiere esfuerzo.

Tiene algunas opciones (buenas):

1) agregue un campo a la entrada de enrutamiento, para indicar que se ha inicializado. Es probable que esto no haga que la estructura sea más grande, debido a los requisitos de relleno y alineación de su implementación:

struct routing_entry {
    unsigned long destSeq;  // 32 bits on Win32. Could be different.
    unsigned long nextHop   // 32 bits on Win32. Could be different.
    unsigned char hopCount; // 8 bits on all modern CPUs. Could be different.
    unsigned char initialized; // ditto
};

¿Por qué no usar bool? Porque el estándar permite útilmente sizeof (bool)! = 1 . Es completamente posible que un bool se implemente como int, especialmente si tiene un compilador C ++ antiguo. Eso haría que tu estructura sea más grande.

Luego, asegúrese de que la estructura esté iniciada con valores 0 en su función, en lugar de cualquier basura que haya en la pila:

routing_entry Cnode_router_aodv::consultTable(unsigned int destinationID ) {    
    routing_entry route = {};

    if ( routing_table.find(destinationID) != routing_table.end() )
        route = routing_table[destinationID];

    return route; // will be "empty" if not found
}

Y asegúrese de que todas las entradas en el mapa tengan el campo inicializado establecido en un valor distinto de cero. La persona que llama luego comprueba inicializado.

2) Use " magic " valores de los campos existentes como marcadores.

Supongamos, por razones de argumento, que nunca tratas en rutas con hopCount 0. Luego, siempre que inicialices 0 como se indica arriba, las personas que llaman pueden verificar hopCount! = 0. Los valores máximos de los tipos también son buenos valores de indicador: Dado que está restringiendo sus rutas a 256 saltos, es probable que no haga ningún daño al restringirlos a 255 saltos. En lugar de que las personas que llaman tengan que recordar esto, agregue un método a la estructura:

struct routing_entry {
    unsigned long destSeq;  // 32 bits
    unsigned long nextHop   // 32 bits
    unsigned char hopCount; // 8 bits
    bool routeFound() { return hopCount != (unsigned char)-1; }
};

Entonces inicializarías así:

routing_entry route = {0, 0, -1};

o si le preocupa lo que sucede cuando cambia el orden o la cantidad de campos en el futuro:

routing_entry route = {0};
route.hopCount = -1;

Y la persona que llama:

routing_entry myroute = consultTable(destID);
if (myroute.routeFound()) {
    // get on with it
} else {
    // destination unreachable. Look somewhere else.
}

3) La persona que llama pasa en un routing_entry por puntero o referencia no constante. Callee llena la respuesta con eso y devuelve un valor que indica si tuvo éxito o no. Esto se llama comúnmente un parámetro "out" porque simula la función que devuelve un routing_entry y un bool.

bool consultTable(unsigned int destinationID, routing_entry &route) {    
    if ( routing_table.find(destinationID) != routing_table.end() ) {
        route = routing_table[destinationID];
        return true;
    }
    return false;
}

La persona que llama hace:

routing_entry route;
if (consultTable(destID, route)) {
    // route found
} else {
    // destination unreachable
}

Por cierto, cuando usa un mapa, su código tal como está busca la ID dos veces. Puedes evitar esto de la siguiente manera, aunque es poco probable que marque una diferencia notable en el rendimiento de su aplicación:

map< unsigned long int, routing_entry >::iterator it =
    routing_table.find(destinationID);
if (it != routing_table.end()) route = *it;

Otra forma es hacer que su función devuelva un valor de estado (HRESULT o similar) que indique si se inicializó, y pase el puntero a la estructura como uno de los parámetros.

En C ++, es común devolver un estado que indica el código de error (o 0 si es correcto), pero esto, por supuesto, depende de sus hábitos de programación.

Simplemente pasar un puntero y verificar nulo funcionaría de todos modos.


shared_ptr<routing_entry> Cnode_router_aodv::consultTable(unsigned int destinationID ) {    
  shared_ptr<routing_entry> route;

  if ( routing_table.find(destinationID) != routing_table.end() )
    route.reset( new routing_entry( routing_table[destinationID] ) );

  return route; // will be "empty" if not found
}

// using
void Cnode_router_aodv::test() 
{
  shared_ptr<routing_entry> r = consultTable( some_value );
  if ( r != 0 ) {
    // do something with r
  }
  // r will be freed automatically when leaving the scope.
}

G'day,

De acuerdo con la mayoría de lo que 1800 tiene que decir, me inclinaría más a hacer que su función consultTable devuelva un puntero a una estructura routing_entry en lugar de un booleano.

Si la entrada se encuentra en la tabla, la función devuelve un puntero a una nueva ruta_enrutamiento. Si no se encuentra, devuelve NULL.

Por cierto, buena respuesta, 1800.

HTH

aplausos,

Como alternativa a la solución de parámetros de entrada - salida, puede seguir los consejos de Uncle Bobs y crear una clase de lector de entrada.

typedef map< unsigned long int, routing_entry > routing_table_type;
routing_table_type routing_table;


//Is valid as long as the entry is not removed from the map
class routing_entry_reader 
{
    const routing_table_type::const_iterator routing_table_entry;  
    const routing_table_type& routing_table;

public: 
    routing_entry_reader( const routing_table_type& routing_table, int destination_id ) 
    : routing_table(routing_table),
      routing_table_entry( routing_table.find(destination_id) ) { 
    }

    bool contains_entry() const { 
        return  routing_table_entry!=routing_table.end(); 
    }

    const routing_entry& entryByRef() const {
        assert(contains_entry());
        return routing_table_entry->second;
    }
};


routing_entry_reader entry_reader(routing_table, destination_id);
if( entry_reader.contains_entry() )
{
    // read the values from the entry
}

En tu método

routing_entry Cnode_router_aodv::consultTable(unsigned int destinationID ) {

    routing_entry route;
    ...
    return route;
}

Está intentando devolver una automática, es decir, el objeto está en el marco de la pila local, objeto. Esto nunca hará lo que quiere que haga, ya que esta memoria no está disponible cuando la función está fuera de alcance.

Deberá crear el objeto y luego devolver el objeto recién creado. Le sugiero que consulte Scott Meyers Effective C ++ Third Edition, Artículo # 21.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top