有关传递对象的C++约定(指针vs引用)

3

我想就向函数/方法传递参数的惯例进行探讨,这与IT技术有关。我知道这是一个常见问题,并且已经被回答了很多次,但是我搜索了很多,没有找到完全满意我的答案。

按值传递显而易见,我不会再提及此点。我的建议如下:

  1. 通过非const引用传递,表示对象会被修改
  2. 通过const引用传递,表示对象会被使用
  3. 通过指针传递,表示将存储对对象的引用,是否传递所有权取决于上下文。

这似乎是一致的,但是当我想选择堆分配对象并将其传递给第2个参数时,它会像这样:

void use(const Object &object) { ... }

//...

Object *obj = getOrCreateObject();
use(*obj);

或者

Object &obj = *getOrCreateObject();
use(obj);

这两个选项对我来说都很奇怪。你有什么建议吗?

PS:我知道应该避免使用裸指针,而应该使用智能指针(更容易的内存管理和所有权表达),这可能是我正在工作的项目重构的下一步。


问题在于:(1)C++没有通用的约定,(2)现有的约定取决于语言标准或项目范围,(3)C++语言没有表达对象生命周期约束、不可变性和许多其他事物的手段。因此,没有任何约定是足够好的。唯一确定的是,从C++11开始,建议使用智能指针而不是原始指针来表示所有权。所以,是的,这是一个好主意。 - user7860670
我知道,对于惯例的讨论甚至可能会引发圣战。那么在一份良好编写的代码中,我所描述的哪种替代方式更为常见?是 *obj = get() 还是 &obj = *get() - PKua
@PKua 优秀的代码可能会使用类型擦除来按值返回。 - juanchopanza
2
如果您返回一个原始指针,那么 Object &obj = *getOrCreateObject(); 将不是一个好选择,因为函数可以合法地返回一个空指针,这样您就会得到无效的引用。如果该函数不应返回空指针值,则返回指针而不是引用没有任何意义。 - user7860670
@VVT 很好的观点。我没有考虑过返回一个引用。所以如果我想要操作特定的对象而不是副本,它会变成 Object &obj = getOrCreateObject(); 对吗? - PKua
3个回答

1
您可以使用这些约定。但请注意,在处理其他人编写的代码时,不能假定约定。您也不能假定阅读您的代码的人知道您的约定。当接口可能存在歧义时,应通过注释记录它。
通过指针传递意味着对象将被存储。谁是其所有者取决于上下文。
我只能想到一个上下文,即智能指针的构造函数,其中指针参数的所有权应转移到被调用者。
除了可能意图存储之外,指针参数还可以具有与引用参数相同的含义,并增加了该参数是可选的附加项。通常,您无法使用引用表示可选参数,因为它们不能为null - 虽然对于自定义类型,您可以使用对sentinel值的引用。
都看起来很怪异。您会给予什么建议?
对我来说,两者都不奇怪,所以我的建议是习惯使用。

我知道我不能假设惯例。我想要弄清楚它们,与他人讨论并开始相应地使用。我应该写“对象的引用将被存储” - 像unique_ptr一样的唯一一个,还是像shared_ptr一样的多个之一。我会进行编辑。 - PKua

1
您的规范的主要问题在于,您没有考虑到接口可能与不遵循您规范的代码(例如由其他人编写的代码)进行交互的可能性。
一般来说,我使用不同的规范,并且很少需要绕过它们。(主要例外情况是如果需要使用指向指针的指针,但我很少直接需要这样做。)
如果以下任何一个可能为真,则通过非const引用传递是合适的;
  • 对象可能会被改变;
  • 对象可能通过非const引用传递给另一个函数[当使用选择省略const的开发人员编写的第三方代码时,这是相关的 - 这实际上是很多初学者或懒惰的开发人员做的事情];
  • 对象可能通过非const指针传递给另一个函数[当使用选择省略const的开发人员编写的第三方代码,或者使用遗留API时,这是相关的];
  • 对象的非const成员函数被调用(无论它们是否改变对象)[当使用第三方代码的开发人员更喜欢避免使用const时,这也经常是需要考虑的]。

相反,如果以下所有条件都为真,则可以传递const引用;

  • 对象的非mutable成员未被更改;
  • 对象仅通过const引用、const指针或值传递给其他函数;
  • 仅调用对象的const成员函数(即使这些成员能够更改mutable成员)。

如果函数会复制对象,我会选择按值传递而不是按const引用传递。 (例如,我不会先按const引用传递,然后在函数内部构造传递对象的副本)。

如果适当情况下需要传递非const引用,但也有可能不传递对象(例如nullptr),则传递非const指针很重要。

如果适当情况下需要传递const引用,但也有可能不传递对象(例如nullptr),则传递const指针很重要。

我不会更改以下任何一种惯例。

  • 在函数内部存储对象的引用或指针以供以后使用——可以将指针转换为引用或反之。并且两者都可以被存储(可以分配一个指针,也可以使用引用来构造一个对象);
  • 区分动态分配和其他对象——由于我通常要么根本不使用动态内存分配(例如使用标准容器,并通过引用传递它们或仅传递它们中的迭代器),要么——如果我必须直接使用new表达式——将指针存储在另一个负责释放的对象中(例如一个std::smart_pointer),然后传递包含对象。

0
在我看来,它们是一样的。在你的帖子的第一部分中,你谈论的是函数签名,但你的例子是关于函数调用的。

我展示了这样一个签名所暗示的可能函数调用结构。 - PKua

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