Возвращая struct из функции, как я могу проверить, что она инициализирована? - PullRequest
5 голосов
/ 19 июня 2009

У меня есть следующая структура в C ++:

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

И у меня есть следующая функция:

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" - это stl :: map, определенная следующим образом:

map< unsigned long int, routing_entry > routing_table;

Мой вопрос теперь заключается в том, что при использовании функции consultTable я хочу проверить, что возвращаемое значение действительно инициализировано, как в псевдокоде Java (потому что я из лагеря Java):

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

затем проверка, если r == нуль

Ответы [ 9 ]

15 голосов
/ 19 июня 2009

Здесь есть несколько проблем. Наиболее неотложной может быть ситуация, когда идентификатор пункта назначения не найден. Поскольку у вас нет конструктора в routing_entry и вы не инициализируете по умолчанию, он будет иметь неопределенные значения.

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

Одним из способов справиться с этим является инициализация по умолчанию. Это работает, давая указание компилятору заполнить структуру нулями. Это своего рода прием, заимствованный из C, но здесь он хорошо работает.

routing_entry route={0};

Вы упомянули, что вы пришли из Java, в отличие от Java, структура и члены класса не инициализируются 0, так что вам действительно нужно как-то с этим справиться. Другой способ - определить конструктор:

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
};

Также обратите внимание, что в C ++ размер членов типа integer и char не определяется в битах. Тип символа - 1 байт (но байт не определен, но обычно 8 бит). Длинные значения обычно составляют 4 байта в наши дни, но могут иметь и другое значение.

Переход к вашему consultTable с фиксированной инициализацией:

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
}

Один из способов определить это - проверить, обнуляется ли структура. Я предпочитаю рефакторинг, чтобы функция возвращала bool для обозначения успеха. Кроме того, для простоты я всегда печатаю структуры STL, поэтому я сделаю это здесь:

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

Затем мы передаем ссылку на запись маршрутизации для заполнения. Это может быть более эффективным для компилятора, но, возможно, это не имеет значения - в любом случае это всего лишь один метод.

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;
}

Вы бы назвали это так:

routing_entry entry={0};
if (consultTable(id, entry))
{
  // do something with entry
}
3 голосов
/ 19 июня 2009

Лучший способ, который я нашел для этого, это использовать boost :: необязательный , который предназначен именно для решения этой проблемы.

Ваша функция будет выглядеть примерно так: -

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>()
}

И ваш код вызова выглядит как

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

Как правило, использование параметров «out» (передача указателя или ссылки на объект, который затем «заполняется» вызываемой функцией) не одобряется в C ++. Подход, который все «возвращает» функцией содержится в возвращаемом значении, и то, что никакие параметры функции не изменены, может сделать код более читабельным и поддерживаемым в долгосрочной перспективе.

2 голосов
/ 19 июня 2009

Это типичное решение для вашей проблемы:

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;
}

Вместо указателя вы можете использовать ссылку; это вопрос стиля.

1 голос
/ 19 июня 2009

Первое замечание: в C ++, в отличие от Java, пользователи могут определять типы значений. Это означает, что существует 2 ^ 32 * 2 ^ 32 * 2 ^ 8 возможных значений для routing_entry. Если вы хотите, вы можете думать о routing_entry как о 72-битном примитивном типе, хотя вы должны быть немного осторожны с аналогией.

Таким образом, в Java route может быть нулевым, и для переменной routing_entry есть 2 ^ 32 * 2 ^ 32 * 2 ^ 8 + 1 полезных разных значений. В C ++ он не может быть нулевым. В Java «пустой» может означать возврат пустой ссылки. В C ++ только указатели могут быть нулевыми, а routing_entry не является типом указателя. Так что в вашем коде в данном случае «пустой» означает «я понятия не имею, какое значение имеет эта вещь, потому что я никогда ее не инициализировал и не присвоил».

В Java объект routing_entry будет размещен в куче. В C ++ вы не хотите делать это без необходимости, потому что управление памятью в C ++ требует усилий.

У вас есть несколько (хороших) вариантов:

1) добавить поле в запись маршрутизации, чтобы указать, что оно было инициализировано. Скорее всего, это не сделает структуру больше из-за требований вашей реализации к отступам и выравниванию:

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
};

Почему бы не использовать bool? Потому что стандарт услужливо позволяет sizeof(bool) != 1. Вполне возможно, что bool реализован как int, особенно если у вас старый компилятор C ++. Это сделало бы вашу структуру больше.

Затем убедитесь, что структура содержит 0 значений в вашей функции, а не мусор в стеке:

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
}

И убедитесь, что у всех входов в карте инициализированное поле установлено в ненулевое значение. Вызывающий затем проверяет инициализацию.

2) Использовать «магические» значения существующих полей в качестве маркеров.

Предположим, что вы никогда не имеете дело с маршрутами с hopCount 0. Затем, пока вы инициализируете 0, как описано выше, вызывающие могут проверять hopCount! = 0. Максимальные значения типов также являются хорошими значениями флагов поскольку вы ограничиваете свои маршруты 256 прыжками, скорее всего, вы не будете причинять никакого вреда, ограничив их 255 прыжками. Вместо того, чтобы вызывать это нужно, добавьте метод в структуру:

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; }
};

Тогда вы бы инициализировали так:

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

или, если вас беспокоит, что произойдет, если вы в будущем измените порядок или количество полей:

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

А звонящий делает:

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

3) Вызывающий абонент передает routing_entry по указателю или неконстантной ссылке. Callee заполняет ответ и возвращает значение, указывающее, успешно ли оно выполнено или нет. Обычно это называется «out param», потому что он имитирует функцию, возвращающую routing_entry и a 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;
}

Звонящий делает:

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

Кстати, при использовании карты ваш код в том виде, как он есть, дважды ищет идентификатор. Вы можете избежать этого следующим образом, хотя вряд ли это заметно повлияет на производительность вашего приложения:

map< unsigned long int, routing_entry >::iterator it =
    routing_table.find(destinationID);
if (it != routing_table.end()) route = *it;
0 голосов
/ 20 июня 2009

В вашем методе

routing_entry Cnode_router_aodv::consultTable(unsigned int destinationID ) {

    routing_entry route;
    ...
    return route;
}

Вы пытаетесь вернуть автоматический объект, то есть объект находится в фрейме локального стека, объект. Это никогда не будет делать то, что вы хотите, потому что эта память недоступна, когда функция выходит из области видимости.

Вам нужно будет создать объект, а затем вернуть вновь созданный объект. Предлагаю вам проконсультироваться со Скоттом Мейерсом, Эффективное C ++, третье издание, № 21.

0 голосов
/ 19 июня 2009

G'day,

Соглашаясь с большей частью того, что говорит 1800, я был бы более склонен заставить вашу функцию consultTable возвращать указатель на структуру routing_entry, а не на логическое значение.

Если запись найдена в таблице, функция возвращает указатель на новый routing_entry. Если он не найден, возвращается NULL.

Кстати, хороший ответ, 1800.

НТН

ура

0 голосов
/ 19 июня 2009

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.
}

0 голосов
/ 19 июня 2009

В качестве альтернативы решению параметра ввода-вывода вы можете последовать совету дяди Бобса и создать класс для чтения записей.

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
}
0 голосов
/ 19 июня 2009

Другой способ - заставить вашу функцию возвращать значение состояния (HRESULT или подобное), указывающее, была ли оно инициализировано, и передавать указатель на структуру в качестве одного из параметров.

В C ++ обычно возвращают статус, указывающий код ошибки (или 0 в случае успеха), но это, конечно, зависит от ваших привычек программирования.

В любом случае, простая передача указателя и проверка на ноль сработают.

...