返回对临时对象的引用

5

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

这里的问题是:
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数据成员的const引用。如果是默认情况应该怎么办?如果我把它省略,编译器(使用-Wall -Wextra -pedantic的GCC)会报错,而我不想让它这样做,因为这些选项往往会以最奇怪的方式捕捉到我的错误设计选择 :)

谢谢!

6个回答

10

同时将默认值作为成员变量,返回对其的引用。当然,前提是默认情况是理论上可行的。如果不可行,就抛出异常并且不返回任何内容。

default:
   throw invalid_argument_exception();

静态局部变量似乎是更好的选择。 - tenfour
1
@tenfour:这可能是,但只有在默认情况理论上可能的情况下才是如此。否则就是浪费。 - Armen Tsirunyan
太棒了!我为什么没想到呢 :) 默认情况确实在理论上是不可能的,在我的情况下指向程序员错误。 - rubenvb
请注意,如果您需要返回一个非const引用,则返回成员/静态局部变量的引用可能效果不是很好(这可能是一个小的代码异味)。 - outis

4
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; }。这样可以延迟构建,直到需要为止。 - MSalters

0

你不能返回一个在栈上创建的临时对象的引用。它将在函数返回时被销毁,导致应用程序崩溃。

如果你打算这样做,你必须通过值而不是引用返回,例如:

stringSet Target::dirList( const dirType type ) const

这显然会对性能产生影响,因为很可能会调用其他引用的复制构造函数。另一种选择是避免在栈上创建临时对象。根据你的应用程序,有几种方法可以实现,比如拥有一个简单的池,从中获取临时对象,并在某个时刻进行垃圾回收,或者让dirList接受一个由函数填充的stringSet参数。

最好的情况是 - 你不能只在某个地方有一个永久的默认集合吗?它必须每次调用都是唯一的吗?


0

不一定总是正确的选择,但是你可以使用shared_ptr来实现这个功能——如果字符串集合已经存在,则构造一个具有空删除器的shared_ptr并返回它,否则构造一个指向空集合并具有普通删除器的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;
}

0

在一个不可能被执行的默认处理程序中,您可以放置指令来表明它是无法到达的。这是编译器特定和不可移植的,但大多数编译器都有类似的功能。我只是不记得GCC如何拼写这个。

编辑 找到了语法...

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

就像我说的那样,这是GCC特有的功能 - 不过Visual C++有一个不同拼写的等效功能。

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

编辑 或者抛出异常。

无论哪种方式(也许除了异常),只有当你真的确定它永远不会发生时才这样做。


-2

如果我理解你的意思是..........
以下是我的开关实现方式。
关于此代码的两点说明:
1. 我不喜欢使用"&"引用,更喜欢"* 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;
}

为什么使用指针,而不是引用就足够了呢?没有delete的麻烦(如果你忘记在需要的地方写它们),也不用担心NULL指针……我知道它们本质上是一样的,但我更喜欢operator.operator->。就是这样。 (无意冒犯) - rubenvb
@rubenvb:你肯定有道理,但我更喜欢使用指针和NULL,因为NULL可以表示错误,而不必处理异常。我想这是一种偏好的编码风格问题。 - Poni

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接