在C++中什么时候应该传递引用,什么时候应该传递指针?

51

常见情况:

  1. 将std::string传递给函数foo(std::string*)或foo(std::string&)。
  2. 将tr1::shared_ptr传递给函数foo(tr1::shared_ptr* ptr)或foo(tr1::shared_ptr& ptr)。

通常情况下,什么做法是好的?我总是感到困惑。一开始,把所有东西都作为引用传递似乎很一致,但无法将文字量或空指针作为引用传递。

同样,将所有东西作为指针也很好,但我必须担心指针可能指向NULL,并在该函数开头检查这些条件。

你认为以下代码片段是否好?

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <tr1/memory>
#include <algorithm>
using namespace std;
using namespace std::tr1;

int main(){
        map<string, shared_ptr<vector<string> > > adjacencyMap;
        vector<string>* myFriends = new vector<string>();
        myFriends->push_back(string("a"));
        myFriends->push_back(string("v"));
        myFriends->push_back(string("g"));
        adjacencyMap["s"] = shared_ptr<vector<string> >(myFriends);
        return 0;
}

感谢 Ajay


foo(tr1::shared_ptr<string>* ptr) 从我看来,这个设计非常不好。你了解常量引用吗? - Anycorn
3
为什么不立即把“myFriends”放入智能指针中? - GManNickG
2
我认为这是如何在C++中将对象传递给函数?的重复问题,我已经回答了这个一般性问题。 - sbi
你也可以不使用shared_ptr(或者new),请看我的回答... - Matthieu M.
8个回答

30
一个好的经验法则是:“尽可能使用引用,必须时再使用指针”。

29

参考文献更容易正确处理。

您对文字的问题是因为您没有使用const引用吗?您不能将临时值(由文字产生)绑定到非const引用上,因为更改它是没有意义的。但您可以将其绑定到const引用上。

特别是在将参数传递给函数,并且该函数不会更改该参数且不是内置类型时,请通过const引用传递。它的工作方式与按值传递类似,只是它不需要复制构造函数调用。

指针很有用,因为它们具有保证无效值的值,这样您就可以进行测试。有时这是无关紧要的,有时则非常重要。当然,通常情况下,您无法通过指针传递文字,除非(在字符串常量的情况下)已经是。

一些编码标准说,永远不应通过非const引用传递任何内容,因为在调用点提供的参数没有指示可能被函数更改。在这种情况下,您将被要求通过指针传递。我不喜欢这个,特别是随着编程工具变得越来越容易获取函数签名,以便您可以查看函数是否可能更改参数。但是,在团队或企业中工作时,样式一致性比任何个人样式元素更重要。


7
在我之前的工作中,我们有一个规定,几乎从不使用普通引用。相反,我们达成了以下共识:
  • 按值传递(对于易于复制的对象、所有基本类型、小值类型、std::string、非常小或带引用计数的字符串)
  • const引用传递(用于只读访问大型对象)
  • 如果需要读写访问,则按指针传递
如果每个人都遵循这些规则,你可以假设传递给函数的参数在没有取地址的情况下不会被修改。这对我们起作用了。

为什么要按值传递std::string?应该按const引用传递。 即使进行读写访问,也应默认通过引用传递。 - ronag
如果您想在函数内修改一个原始类型,该怎么办?您的第一点如何满足这种情况? - user855
+1,非常明智。 @aja 通过指针传递(数字3) @ron 数字2这样说。 “应该”实际上是一种偏好。 - Anycorn
@ronag:如果你传递字符串,通常希望使用按值传递的语义。std::string对小字符串有特殊处理,使得复制变得更加便宜。我还没有遇到过优化这一点有所帮助的情况。 - user180326
特殊处理是实现特定的,我认为自从多线程变得更加常用以来,没有现代实现再做这件事情了(我可能错了)。即使有些实现确实这样做了,也总是有额外开销的...而且对于大字符串又要怎么处理呢? - ronag
1
我一开始以为是引用计数,但经过一些搜索,发现这已经过时了。我会进行编辑。 - user180326

5
我真的不明白你为什么要费这么大的劲:
std::map < std::string, std::vector<std::string> > adjacencyMap;
std::vector<std::string>& sFriends = adjacencyMap["s"];
sFriends.push_back("a");
sFriends.push_back("v");
sFriends.push_back("g");

为什么你要在这里使用 shared_ptr?这种情况显然不需要它!


3

作为一般准则,请尽可能通过const引用传递参数。传递指针可能会导致所有权问题以及其他一些微妙错误的可能性。

NULL的目的是什么?用于指示无效的指针/对象。如果你要将无效对象传递给函数,那么你只需要有一种检查对象有效性的方法即可。例如:

void myfunc(const obj& myobj)
{
  if(myobj.valid())
    // DO SOMETHING
}

原始类型通常应该按值传递,因为开销很小。而且这通常是使用字面量的时候。对于字符串,应该尽可能使用 std::string,并尽量远离 const char* C风格字符串。当然,如果必须使用C字符串,则只能使用指针,但总体来说,引用应该是更好的选择。

哦,为了真正实现异常安全,请尽量避免这种情况:

vector<string>* myFriends = new vector<string>();
...
adjacencyMap["s"] = shared_ptr<vector<string> >(myFriends);

改为:

shared_ptr<vector<string> > myFriends(new vector<string>());

请了解 RAII 和异常安全性,了解为什么这是首选方法。

3

也许不是问题的答案。只需保持简单。

int main()
{
        multimap<string, string> adjacencyMap;
        adjacencyMap.insert(std::make_pair("s", "a"));
        adjacencyMap.insert(std::make_pair("s", "v"));
        adjacencyMap.insert(std::make_pair("s", "g"));
        return 0;
}

谢谢!虽然这并没有回答我的问题,但我不知道multimap。而且这种用法似乎非常适合这种情况。 - user855
顺便提一下,这种方法似乎存在内存消耗问题。当使用 multimap 时,相同的键会被重复存储多次。在我的方法中,由于键是唯一的,它们只会被存储一次,因此空间使用更少。对此有什么想法吗? - user855
那是具体实现问题。我相信大多数实现只会在插入时存储一个键。 - ronag
我不清楚这个,当迭代时,你会得到一个对一对的引用,如果他们正在实现你所说的优化,那么他们很难返回它。 - Matthieu M.

3

1

我更喜欢

    map<string, shared_ptr<vector<string> > > adjacencyMap;
    shared_ptr<vector<string> > myFriends(new vector<string>());
    myFriends->push_back(string("a"));
    myFriends->push_back(string("v"));
    myFriends->push_back(string("g"));
    adjacencyMap["s"] = myFriends;
    return 0;

这样可以确保您的本地变量处理是异常安全的。

不过,我真的看不出这如何解决您关于引用与指针优点的问题。在您提到的这两个例子中,我都会使用第二种(引用)形式。


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