【问题标题】:Returning struct from a function, how can I check that it is initialized?从函数返回结构,如何检查它是否已初始化?
【发布时间】:2009-06-19 09:52:09
【问题描述】:

我在 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 == null

【问题讨论】:

  • 谢谢大家,我得到了很多非常有帮助的 cmets,他们真的让我大开眼界。我是 C++ 的新手,但我坚持使用它,因为我正在为我的主人使用遗留代码。最后,我必须选择一个答案,但我赞成所有其他激发我灵感的答案。再次感谢:)

标签: c++ null


【解决方案1】:

这里有一些问题。最紧急的可能是找不到目标 ID 时会发生什么。由于您在 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++ 中,整数和字符成员的大小不是以位为单位定义的。 char 类型是 1 个字节(但一个字节是一个未定义的,但通常是 8 位)。如今,long 通常是 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 来表示成功。此外,为了简单起见,我总是 typedef 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
}

【讨论】:

  • 这看起来不错。另一种方法是,如果您真的想返回 routing_entry,则在您的结构上有一个名为 empty() 的函数,或者返回成员值是否仍与其默认值相同的函数。
  • 这真的很有帮助 :) 你让我看到了很多东西!
  • +1:小点:初始化程序中不需要 0:routing_entry entry = {};很好。
【解决方案2】:

我找到的最佳方法是使用boost::optional,它旨在完全解决这个问题。

你的函数看起来像这样:-

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++ 中是不受欢迎的。所有内容都由函数“返回”的方法包含在返回值中,不修改函数参数可以使代码在长期内更具可读性和可维护性。

【讨论】:

    【解决方案3】:

    这是您问题的典型解决方案:

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

    您可以使用引用来代替指针;这是风格问题。

    【讨论】:

      【解决方案4】:

      首先请注意,在 C++ 中,与 Java 不同,用户可以定义值类型。这意味着一个 routing_entry 有 2^32 * 2^32 * 2^8 个可能的值。如果您愿意,您可以将 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
      };
      

      为什么不使用布尔值?因为该标准有助于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。被调用者将答案填入其中,并返回一个值,指示它是否成功。这通常称为“输出参数”,因为它有点模拟返回 routing_entry 布尔值的函数。

      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
      }
      

      顺便说一句,在使用地图时,您的代码会查找 ID 两次。你可以避免这种情况 如下所示,尽管它不太可能对您的应用性能产生明显影响:

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

      【讨论】:

      • 谢谢,这很有帮助!
      【解决方案5】:

      另一种方法是让你的函数返回一个状态值(HRESULT 或类似的),指示它是否已初始化,并将指向结构的指针作为参数之一传递。

      在 C++ 中,通常会返回指示错误代码的状态(如果成功则返回 0),但这当然取决于您的编程习惯。

      简单地传递一个指针并检查 null 无论如何都可以工作。

      【讨论】:

        【解决方案6】:
        
        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.
        }
        
        

        【讨论】:

          【解决方案7】:

          生日,

          同意 1800 的大部分内容,我更倾向于让您的函数 ConsultTable 返回指向 routing_entry 结构的指针,而不是布尔值。

          如果在表中找到该条目,该函数返回一个指向新路由条目的指针。如果没有找到,则返回NULL。

          顺便说一句,好答案,1800。

          HTH

          干杯,

          【讨论】:

          • 我认为最近的 Java -> C++ 转换应该做的最后一件事是编写返回指向新分配的堆对象的指针的函数。内存泄漏啊! ;-)
          • @1by1,这是真的。如果他们一开始就无法发现内存泄漏,而将头埋在沙子里并忽略内存管理,可能会更糟?他在 C++ 领域。精灵从瓶子里出来了! (-:
          • 我一直在遭受内存泄漏的困扰,但我最近发现了 valgrind,对我有所帮助!
          【解决方案8】:

          作为输入-输出参数解决方案的替代方案,您可以按照 Uncle Bobs 的建议创建一个入口阅读器类。

          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
          }
          

          【讨论】:

            【解决方案9】:

            在你的方法中

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

            您正在尝试返回一个自动的,即对象在本地堆栈框架上,对象。这永远不会做你想让它做的事情,因为当函数超出范围时,这个内存不可用。

            您需要创建对象,然后返回新创建的对象。我建议您参考 Scott Meyers Effective C++ 第三版,第 21 条。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-06-01
              • 1970-01-01
              • 1970-01-01
              • 2014-08-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-01-25
              相关资源
              最近更新 更多