con*_*nec 366 c++ pointers reference
我理解指针与引用的语法和一般语义,但是我应该如何决定何时在API中使用引用或指针?或多或少?
当然,有些情况需要一个或另一个(operator++需要一个引用参数),但总的来说,我发现我更喜欢使用指针(和const指针),因为语法清楚地表明变量是破坏性地传递的.
例如,在以下代码中:
void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
int a = 0;
add_one(a); // Not clear that a may be modified
add_one(&a); // 'a' is clearly being passed destructively
}
Run Code Online (Sandbox Code Playgroud)
使用指针,它总是(更加)明显地发生了什么,所以对于API等,清晰度是一个大问题,指针不是比引用更合适?这是否意味着只应在必要时使用引用(例如operator++)?是否有任何性能问题?
编辑(已完成):
除了允许NULL值和处理原始数组之外,似乎选择归结为个人偏好.我已经接受了下面引用Google的C++样式指南的答案,因为它们提出了"引用可能令人困惑,因为它们具有值语法但指针语义"的观点.
由于清理不应该为NULL的指针参数所需的额外工作(例如,add_one(0)将调用指针版本并在运行时中断),从可维护性的角度来看,使用必须存在对象的引用是有意义的,尽管这是一种耻辱失去句法的清晰度.
Kla*_*aim 285
尽可能使用参考,指向任何地方的指针.
避免指针,直到你不能.
原因是指针比其他任何结构都更难以跟踪/阅读,安全性更低,操作更危险.
所以经验法则是只有在没有其他选择时才使用指针.
例如,当函数在某些情况下可以返回nullptr并且假设它将返回时,返回指向对象的指针是一个有效选项.也就是说,更好的选择是使用类似的东西boost::optional.
另一个例子是使用指向原始内存的指针来进行特定的内存操作.这应隐藏并本地化在代码的非常狭窄的部分,以帮助限制整个代码库的危险部分.
在您的示例中,使用指针作为参数没有意义,因为:
nullptr作为参数,你将进入undefined-behavior-land;如果函数的行为必须使用或不使用给定对象,那么使用指针作为属性表明您可以nullptr作为参数传递,它对于函数来说很好.这是用户和实现之间的契约.
And*_*gia 59
性能完全相同,因为引用在内部实现为指针.因此,您无需担心这一点.
关于何时使用引用和指针,没有普遍接受的约定.在少数情况下,您必须返回或接受引用(例如,复制构造函数),但除此之外,您可以根据需要自由地执行.我遇到的一个相当常见的约定是当参数必须在NULL值为ok时引用现有对象和指针时使用引用.
一些编码约定(如谷歌的)规定应该总是使用指针或const引用,因为引用有一些不清楚的语法:它们具有引用行为但值语法.
Mah*_*esh 32
来自C++ FAQ Lite -
尽可能使用引用,并在必要时使用指针.
只要您不需要"重新安装",引用通常优先于指针.这通常意味着引用在类的公共接口中最有用.引用通常出现在对象的皮肤上,而指针则出现在内部.
上面的例外是函数的参数或返回值需要"sentinel"引用 - 一个不引用对象的引用.这通常最好通过返回/获取指针,并赋予NULL指针这一特殊意义(引用必须始终为别名对象,而不是取消引用的NULL指针).
注意:旧的C语言程序员有时不喜欢引用,因为它们提供了调用者代码中不明确的引用语义.然而,在一些C++经验之后,人们很快意识到这是一种信息隐藏形式,这是一种资产而不是责任.例如,程序员应该用问题的语言而不是机器的语言编写代码.
Cal*_*ius 15
我的经验法则是:
&)const是传入的参数)const T&),则使用对传入参数的引用.int ¤t = someArray[i])无论您使用哪一个,如果它们不明显,请不要忘记记录您的功能及其参数的含义.
bar*_*gol 13
免责声明:除了引用不能为NULL或"反弹"(意味着不能改变它们的别名的对象)这一事实,它真的归结为品味问题,所以我不会说"这个更好".
也就是说,我不同意你在帖子中的最后一句话,因为我不认为代码在参考文献中失去了清晰度.在你的例子中,
add_one(&a);
Run Code Online (Sandbox Code Playgroud)
可能比清楚
add_one(a);
Run Code Online (Sandbox Code Playgroud)
因为你知道a的价值很可能会发生变化.另一方面,功能的签名
void add_one(int* const n);
Run Code Online (Sandbox Code Playgroud)
有点不清楚:是n将是一个整数还是一个数组?有时您只能访问(文档记录不清晰)标题和签名
foo(int* const a, int b);
Run Code Online (Sandbox Code Playgroud)
乍一看并不容易解释.
Imho,当没有(重新)分配和重新绑定(在之前解释的意义上)需要时,引用和指针一样好.此外,如果开发人员仅使用指针作为数组,则函数签名不那么模糊.更不用说运算符语法对引用更具可读性这一事实.
pae*_*bal 12
与其他人一样已经回答:一定要用引用,除非该变量的存在NULL/ nullptr是真正有效的状态.
约翰卡马克关于这个问题的观点是相似的:
NULL指针是C/C++中最大的问题,至少在我们的代码中是这样.单个值作为标志和地址的双重使用会导致令人难以置信的致命问题.只要有可能,C++引用应该优于指针; 虽然引用"真的"只是一个指针,但它具有非NULL的隐式契约.当指针变为引用时执行NULL检查,然后您可以忽略此问题.
http://www.altdevblogaday.com/2011/12/24/static-code-analysis/
用户Bret Kuhns正确地评论道:
C++ 11标准已经完成.我认为现在是时候在这个帖子中提到大多数代码都应该完美地使用引用,shared_ptr和unique_ptr的组合.
确实如此,但问题仍然存在,即使用智能指针替换原始指针也是如此.
例如,无论是std::unique_ptr和std::shared_ptr可以设计为通过其默认的构造函数"空"指针:
...意味着使用它们而不验证它们不是空的可能会导致崩溃,这正是J. Carmack讨论的全部内容.
然后,我们有一个有趣的问题:"我们如何将智能指针作为函数参数传递?"
Jon对C++问题的回答- 将引用传递给boost :: shared_ptr,以下注释显示即使这样,通过复制或引用传递智能指针并不像人们想的那样明确(我赞成自己" by-reference"默认情况下,但我可能是错的).
小智 7
这不是品味问题.这里有一些明确的规则.
如果要在声明它的范围内引用静态声明的变量,那么使用C++引用,它将是非常安全的.这同样适用于静态声明的智能指针.通过引用传递参数是此用法的示例.
如果你想引用范围比声明范围更宽的范围,那么你应该使用引用计数的智能指针来保证它是完全安全的.
您可以引用集合的元素,并提供语法方便的参考,但它并不安全; 该元素可以随时删除.
要安全地保存对集合元素的引用,必须使用引用计数的智能指针.
“尽可能使用引用”规则存在问题,如果您想保留引用以供进一步使用,则会出现此问题。为了用示例来说明这一点,假设您有以下课程。
class SimCard
{
public:
explicit SimCard(int id):
m_id(id)
{
}
int getId() const
{
return m_id;
}
private:
int m_id;
};
class RefPhone
{
public:
explicit RefPhone(const SimCard & card):
m_card(card)
{
}
int getSimId()
{
return m_card.getId();
}
private:
const SimCard & m_card;
};
Run Code Online (Sandbox Code Playgroud)
RefPhone(const SimCard & card)起初,在构造函数中通过引用传递参数似乎是一个好主意,因为它可以防止将错误/空指针传递给构造函数。它以某种方式鼓励在堆栈上分配变量并从 RAII 中受益。
PtrPhone nullPhone(0); //this will not happen that easily
SimCard * cardPtr = new SimCard(666); //evil pointer
delete cardPtr; //muahaha
PtrPhone uninitPhone(cardPtr); //this will not happen that easily
Run Code Online (Sandbox Code Playgroud)
但随后,临时的事情就会摧毁你的幸福世界。
RefPhone tempPhone(SimCard(666)); //evil temporary
//function referring to destroyed object
tempPhone.getSimId(); //this can happen
Run Code Online (Sandbox Code Playgroud)
因此,如果您盲目地坚持引用,您就会牺牲传递无效指针的可能性来存储对已销毁对象的引用的可能性,这基本上具有相同的效果。
编辑:请注意,我坚持“尽可能使用引用,必须使用指针。除非不能,否则避免使用指针”。来自最受支持和接受的答案(其他答案也表明如此)。虽然这应该是显而易见的,但例子并不表明引用本身是不好的。然而,它们可能会被滥用,就像指针一样,它们可能会给代码带来威胁。
指针和引用之间存在以下差异。
考虑到这些,我目前的规则如下。
任何性能差异都会很小,以至于无法使用不太清楚的方法.
首先,一个未提及参考文献通常优越的案例是const参考文献.对于非简单类型,传递const reference避免创建临时值并且不会引起您关注的混淆(因为该值未被修改).在这里,强制一个人传递一个指针会引起你担心的混乱,因为看到所采用的地址并传递给一个函数可能会让你认为这个值发生了变化.
无论如何,我基本上同意你的看法.我不喜欢函数接受引用来修改它们的值,因为这不是函数正在做的事情.在这种情况下,我也更喜欢使用指针.
当您需要返回复杂类型的值时,我倾向于更喜欢引用.例如:
bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative
Run Code Online (Sandbox Code Playgroud)
这里,函数名称清楚地表明您在数组中获取信息.所以没有混乱.
引用的主要优点是它们总是包含有效值,比指针更清晰,并且支持多态而无需任何额外的语法.如果这些优点都不适用,则没有理由比指针更喜欢引用.
从维基复制-
这样做的结果是,在许多实现中,通过引用对具有自动或静态生存期的变量进行操作,尽管在语法上与直接访问它类似,但可能涉及成本高昂的隐藏取消引用操作。引用是 C++ 语法上有争议的特性,因为它们掩盖了标识符的间接级别。也就是说,与指针通常在语法上很突出的 C 代码不同,在一大块 C++ 代码中,如果正在访问的对象被定义为局部变量或全局变量,或者它是否是对的引用(隐式指针),可能不会立即显而易见。一些其他位置,特别是当代码混合引用和指针时。这方面可能会使写得不好的 C++ 代码更难阅读和调试(请参阅别名)。
我 100% 同意这一点,这就是为什么我认为只有在有充分理由时才应该使用参考文献。
需要记住的要点:
指针可以NULL,引用不能NULL。
引用更容易使用,const当我们不想改变值而只需要函数中的引用时可以使用引用。
*与a 一起使用的 while 引用使用的指针&。
当需要进行指针算术运算时,使用指针。
您可以拥有指向 void 类型的指针int a=5; void *p = &a;,但不能拥有对 void 类型的引用。
指针与引用
void fun(int *a)
{
cout<<a<<'\n'; // address of a = 0x7fff79f83eac
cout<<*a<<'\n'; // value at a = 5
cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);
Run Code Online (Sandbox Code Playgroud)
判断何时使用什么
指针:用于数组、链表、树实现和指针算术。
参考:在函数参数和返回类型中。
| 归档时间: |
|
| 查看次数: |
176616 次 |
| 最近记录: |