【问题标题】:returning a reference to temporary返回对临时的引用
【发布时间】:2024-04-23 17:50:01
【问题描述】:

我知道返回对临时的引用是非法的,但这是我的问题:

const stringSet & Target::dirList( const dirType type ) const
{
    switch( type )
    {
        case SOURCE_DIR:
            return m_sourceDirs;
        case HEADER_DIR:
            return m_headerDirs;
        case RESOURCE_DIR:
            return m_resourceDirs;
        default:
            return stringSet(); // PROBLEM HERE!
    }
}

前三个选项返回一个对stringSet 数据成员的常量引用。默认情况下我该怎么办?如果我忽略它,编译器(带有 -Wall -Wextra -pedantic 的 GCC)会抱怨,我不希望它这样做,因为这些选项往往会以最奇怪的方式抓住我的床设计选择 :)

谢谢!

【问题讨论】:

    标签: c++ reference switch-statement


    【解决方案1】:

    将默认集也保留为成员...并返回对它的引用。当然,如果默认情况在理论上是可能的的话。如果不是,则抛出异常并且不返回任何内容。

    default:
       throw invalid_argument_exception();
    

    【讨论】:

    • 静态本地变量似乎是一个更好的选择。
    • @tenfour:可能是这样,但仅限于理论上可能的默认情况。否则就是浪费
    • 很好的解决方案!为什么我没有想到这一点:)默认情况在理论上确实是不可能的,并且在我的情况下指向程序员错误。
    • 请注意,如果您需要返回非常量引用,返回对成员/静态本地的引用可能效果不佳(可能是轻微的代码异味)。
    【解决方案2】:
    const stringSet & Target::dirList( const dirType type ) const
    {
        static const stringSet defaultSet; // <--
        switch( type )
        {
            case SOURCE_DIR:
                return m_sourceDirs;
            case HEADER_DIR:
                return m_headerDirs;
            case RESOURCE_DIR:
                return m_resourceDirs;
            default:
                return defaultSet; // <--
        }
    }
    

    【讨论】:

    • 最好放在需要的地方:default: { static const stringSet defaultSet; return defaultSet; }。如果需要的话,这将延迟施工直到需要。
    【解决方案3】:

    您不能返回对您在堆栈上创建的临时对象的引用。当你的函数返回时,我将被摧毁,你的应用程序将崩溃。

    如果你打算做这样的事情,你必须按值而不是按引用返回,即

    stringSet Target::dirList( const dirType type ) const

    这显然会影响性能,因为您很可能最终会为您的其他引用调用复制构造函数。另一种方法是避免在堆栈上创建临时对象。根据您的应用程序,有几种方法可以做到这一点,例如有一个简单的池,您可以从中获取临时对象并在某些时候进行垃圾收集,或者您可以让 dirList 采用正在填充的 stringSet 参数由你的功能。

    最好的情况 - 你不能在某个地方设置一个永久的默认值吗?每次调用都必须是唯一的吗?

    【讨论】:

      【解决方案4】:

      不一定总是正确的选项,但为了它的价值,你可以使用 shared_ptr - 构造一个带有 null 删除器的 shared_ptr 并在字符串集已经存在时返回它,否则构造一个指向空集并具有一个普通的删除器并返回它。换句话说:

      #include <set>
      #include <string>
      
      #include <boost/shared_ptr.hpp>
      
      struct NullDeleter
      {
          void operator()(void *p) {}
      };
      
      enum DirType
      {
          SOURCE_DIR,
          HEADER_DIR,
          RESOURCE_DIR,
          OTHER,
      };
      
      typedef std::set<std::string> StringSet;
      typedef boost::shared_ptr<const StringSet> StringSet_CPtr;
      
      struct Target
      {
          StringSet m_sourceDirs, m_headerDirs, m_resourceDirs;
      
          Target()
          {
              m_sourceDirs.insert("/source");
              m_headerDirs.insert("/header");
              m_resourceDirs.insert("/resources");
          }
      
          StringSet_CPtr dir_list(DirType type)
          {
              switch(type)
              {
              case SOURCE_DIR:
                  return StringSet_CPtr(&m_sourceDirs, NullDeleter());
              case HEADER_DIR:
                  return StringSet_CPtr(&m_headerDirs, NullDeleter());
              case RESOURCE_DIR:
                  return StringSet_CPtr(&m_resourceDirs, NullDeleter());
              default:
                  return StringSet_CPtr(new StringSet);
              }
          }
      };
      
      int main()
      {
          Target t;
          StringSet_CPtr  sourceDirs = t.dir_list(SOURCE_DIR),
                          headerDirs = t.dir_list(HEADER_DIR),
                          resourceDirs = t.dir_list(RESOURCE_DIR),
                          otherDirs = t.dir_list(OTHER);
          return 0;
      }
      

      【讨论】:

        【解决方案5】:

        您可以在不可能的情况下默认处理程序中放置一些指令来说明它无法访问。编译器特定且不可移植,但大多数编译器都有一些东西。我只是不记得 GCC 是如何拼写这个的。

        EDIT 找到了语法...

        switch (whatever)
        {
          case blah :
            ...;
            break;
          default :
            __builtin_unreachable ();
        }
        

        正如我所说,这是一个 GCC 特有的功能 - 但是有一个不同拼写的 Visual C++ 等价物。

        顺便说一句 - 总是有return *((stringSet*) 0);

        EDIT或者抛出异常。

        无论哪种方式(也许例外除外),只有当您真的确定它永远不会发生时。

        【讨论】:

          【解决方案6】:

          如果我理解正确的话............
          以下是我使用此类开关的方法。
          关于此代码的两个注意事项:
          1. 我讨厌使用 "&" ref 并且更喜欢 "* const" 一个(更易读),所以请调整。本质上是一样的。
          2. 没有测试这段代码。

          const stringSet * const
          Target::dirList( const dirType type ) const
          {
              const stringSet * pRet = NULL;
          
          
              switch( type )
              {
                  case SOURCE_DIR:
                      stringSet = m_sourceDirs;
                  case HEADER_DIR:
                      stringSet = m_headerDirs;
                  case RESOURCE_DIR:
                      stringSet = m_resourceDirs;
              }
          
          
          
              return pRet;
          }
          

          【讨论】:

          • -1:为什么在引用足够时使用指针?没有delete 混乱(如果你忘记在需要的地方写它们),不用担心NULL 指针......我知道它们本质上是相同的,但我更喜欢operator. 而不是operator-&gt;。那里。我说了。 (无意冒犯)
          • @rubenvb:你肯定明白了,但我对指针和 NULL 很满意,主要是因为 NULL 可能表示错误,而不是搞乱异常。我想这是首选编码风格的问题。