您通常如何返回在本地堆栈上分配的用户定义类型的实例

时间:2009-03-12 18:35:04

标签: c++

我对流行的方法感到好奇。

我发现自己可以互换地做这两件事:

请注意,在所有情况下,对象都是在本地分配的。

std::string GetDescription ()
{
      std::string desc;

     /* Create a description */

       return desc;
}


void GetResult (map<int, double> & resultMap)
{
        /*Fill map result map with  values*/

}

map<int, double> GetResult2(map<int, double> & resultMap)
{
          map<int, double> & resultMap
        /* Fill map result map with  values */
          return resultMap;
}

这样做的首选方式是什么?

8 个答案:

答案 0 :(得分:2)

按值返回或将其作为传入的out参数进行操作。除非您喜欢崩溃和未定义的行为,否则不要通过引用返回在堆栈上分配的任何内容。

编辑:这真的是一种风格问题。我更喜欢值类型的值和非值类型的输出参数。虽然如果我真的想要抽象的东西,我会使用专门的界面,这是ostream的精神。

答案 1 :(得分:1)

我投票支持第一种方法,但是返回一个bool来确认操作是否正确完成:

bool GetResult (map & resultMap)
{
        /Fill map result map with  values/
        return true;
}
如果函数执行的操作可能会对代码库的其余部分产生影响,那么它就会更容易进一步下载。

答案 2 :(得分:1)

对于较大的对象,我倾向于返回一个智能指针,当C ++ 0x被释放并且你有一个支持它的编译器或另一个选择的智能指针时,用std::auto_ptr替换std::unique_ptr。 / p>

此模式也符合强大的异常保证。

std::auto_ptr< std::map<int, double> > GetResult()
{
    std::auto_ptr< std::map<int, double> > result(new std::map<int, double>());
    // Fill result map with values
    return result;
}

答案 3 :(得分:1)

功能界面有两个部分:

  1. 输入,通过参数赋予
  2. 输出,通过返回值赋予
  3. 这种模式当然不是绝对的法则,但它对于一致性(代码美学)和可用性都是一个非常好的规则。除非你有非常充分的理由,否则不要破坏这个架构。

    效率?一般不是一个好理由。通过out参数返回一个值来提高效率是一个非常低级的黑客攻击,它在API的公共接口中没有任何地位。不要使用它。特别是因为它是一个极其过早优化。

    现代C ++编译器会定期执行named return value optimization。很少有情况不能以这种方式进行优化,我怀疑这些情况是否会从一般的争论中受益。

    在少数情况下,这样的out参数至关重要,将方法重构为公共API方法和私有实现,如下所示:

    namespace impl {
        void my_private_impl(args…) {
            …
        }
    }
    
    LargeType my_public_function(args…) {
        LargeType ret;
        impl::my_private_impl(ret, args…);
        return ret;
    }
    

    这样,客户端仍然可以从干净的界面中获利并且获得了性能优势(顺便说一下,无论如何都会内联外部方法,因此 no 使用此模式时的性能损失)。

答案 4 :(得分:0)

一般来说,我赞成第一种方法,因为它使值赋值成为函数调用的结果而不是它的“副作用”。我认为这使代码更容易阅读。

但是,当效率至关重要时,或者创建/分配类型时,我使用out参数方法。对于某些类型的对象(例如容器),使用out参数是合理惯用的,因此这也是一个考虑因素。

答案 5 :(得分:0)

C ++编译器可以优化某些涉及返回值的情况。 See here。我更倾向于返回值,主要是出于美学目的,因为我更喜欢功能风格,但显然你需要小心对象,比如集合,这些对象最终可能会被深深复制。

答案 6 :(得分:0)

对于值类型返回很好。对于对象和容器,通过引用传递是要走的路。返回深层嵌套的容器会导致很多额外的开销,没有任何好处。当你有指针成员变量时,返回非值类型的危险。你想要这些成员的深层或浅层副本吗?如果你不小心,你最终可能会摇摇欲坠。

答案 7 :(得分:0)

这在很大程度上取决于手头的任务。对于创建新值的函数,我会选择第一种方法,同时使用第二种方法来更改参数。正如其他人所提到的,在某些情况下,其他限制(创建/分配成本)会影响决策。

第一个定义清楚地表明它将从无到有创造一个新对象并将返回它。也就是说,相同的定义是说明调用的语义。

第二个和第三个定义要求调用者在调用函数之前创建一个对象。这可能是用户的麻烦,因为她不能忽略返回值(或将其用作提供其他函数的未命名时态,请参阅代码段1),并且可以在情况下受限(即无法从已创建的内容重新分配引用)对象,请参阅代码段2)。

从函数定义中不清楚函数内部参数的用途是什么。函数的行为会根据传递的对象而有所不同吗?从功能定义中不清楚。

我相信,第三种类型是最糟糕的选择。它不仅强制调用者创建一个命名变量来传递给函数,而且还要复制一个副本(除非它是一个拼写错误,你要返回一个[可能是const]引用)

无论如何,其他考虑因素可能会使第一种方法提供的清晰语义的优势失去平衡。在某些情况下,性能或其他限制,如无法复制的对象(请参阅代码段3)

// snippet 1
//    user code to the different approaches
class X {};
X f1();
void f2( X& );
X f3( X& );
void g( X const & );

void test1()
{
   g( f1() ); // valid code
}
void test2()
{
   X x; // must create instance before
   f2( x ); // then call the function
   g( x );
}
void test3()
{
   X x; // must create instance before
   g( f3( x ) );
}

// snippet 2
class Y {};
class X
{
public:
   X( Y & y ) : y_( y ) {}
private:
   Y & y_;
};
X f1();
void f2( X & );
X f3( X & );

void test1()
{
   X x = f1();
}
void test2or3()
{  
   Y y;
   X x( y ); // user must create with a reference
   f2( x ); // f2 cannot change the reference inside x to another instance
}

// snippet 3
class X
{
private:
   X( X const & );
   X& operator= ( X const & );
};
X f1(); // Returned object cannot be used
void f2( X & );
X f3( X & );

void test1()
{
   X x = f1(); // compilation error
}
void test2()
{
   X x;
   f2( x );
}
void test3()
{
   X x;
   X y = f3( x ); // compilation error
}