我对流行的方法感到好奇。
我发现自己可以互换地做这两件事:
请注意,在所有情况下,对象都是在本地分配的。
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;
}
这样做的首选方式是什么?
答案 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)
功能界面有两个部分:
这种模式当然不是绝对的法则,但它对于一致性(代码美学)和可用性都是一个非常好的规则。除非你有非常充分的理由,否则不要破坏这个架构。
效率?一般不是一个好理由。通过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
}