在c++中,通过指针传递比通过引用传递有什么好处?

在c++中,通过指针传递比通过引用传递有什么好处?

最近,我看到了许多选择通过指针传递函数参数而不是通过引用传递函数参数的例子。这样做有好处吗?

例子:

func(SPRITE *x);

伴随着一声呼唤

func(&mySprite);

vs。

func(SPRITE &x);

伴随着一声呼唤

func(mySprite);
146494 次浏览

传递指针

  • 调用者必须取地址->不透明
  • 0值可以表示nothing。这可用于提供可选参数。

通过引用传递

  • 调用方只是将对象->传递为透明。必须用于操作符重载,因为指针类型的重载是不可能的(指针是内置类型)。所以你不能使用指针来做string s = &str1 + &str2;
  • 被调用函数不需要检查0值
  • const的引用也接受临时对象:void f(const T& t); ... f(T(a, b, c));,指针不能这样使用,因为你不能接受临时对象的地址。
  • 最后但并非最不重要的是,引用更容易使用- bug的可能性更小。

指针可以接收NULL形参,而引用形参不能。如果你有机会想要传递“无对象”,那么使用指针而不是引用。

另外,通过指针传递可以让你显式地在调用点看到对象是通过值传递还是通过引用传递:

// Is mySprite passed by value or by reference?  You can't tell
// without looking at the definition of func()
func(mySprite);


// func2 passes "by pointer" - no need to look up function definition
func2(&mySprite);

不是真的。在内部,按引用传递实际上是通过传递被引用对象的地址来执行的。因此,传递一个指针并不能提高任何效率。

不过,通过引用传递确实有一个好处。保证你有一个传入的任何对象/类型的实例。如果传入一个指针,则会有接收到空指针的风险。通过使用引用传递,您将隐式null检查推到函数的调用者的上一层。

Allen Holub在《足够的绳子砸自己的脚》一书中列出了以下2条规则:

120. Reference arguments should always be `const`
121. Never use references as outputs, use pointers

他列出了在c++中添加引用的几个原因:

  • 它们是定义复制构造函数所必需的
  • 它们是操作符重载所必需的
  • const引用允许你使用值传递语义,同时避免复制

他的主要观点是引用不应该被用作“输出”参数,因为在调用点没有指示参数是引用还是值参数。所以他的规则是只使用const引用作为参数。

就我个人而言,我认为这是一个很好的经验法则,因为它可以更清楚地说明参数何时是输出参数。然而,虽然我个人总体上同意这一点,但我确实允许自己受到团队中其他人的意见的影响,如果他们主张将输出参数作为参考(一些开发人员非常喜欢它们)。

以上职位说明:


引用是,保证得到一个非空指针。(尽管我们经常这样对待他们。)

而可怕的坏代码,如带你走出后面的木棚代码,下面将编译&运行:(至少在我的编译器下)

bool test( int & a)
{
return (&a) == (int *) NULL;
}


int
main()
{
int * i = (int *)NULL;
cout << ( test(*i) ) << endl;
};

我对引用的真正问题在于其他程序员,以后称为白痴< em > < / em >,他们在构造函数中分配,在析构函数中释放,且未能提供复制构造函数或操作符=()

突然之间,foo酒吧(酒吧)foo(BAR & BAR)之间有了很大的不同。(自动逐位复制操作被调用。析构函数中的释放会被调用两次。)

值得庆幸的是,现代编译器可以对同一个指针进行双重释放。15年前,他们没有。(在gcc/g++下,使用setenv MALLOC_CHECK_ 0重新使用旧的方法。)结果,在DEC UNIX下,同一内存被分配给两个不同的对象。那里有很多调试的乐趣……


更实际:

  • 引用隐藏了您正在更改存储在其他地方的数据。
  • 引用和复制对象很容易混淆。
  • 指针使它变得明显!

我喜欢“cplusplus.com”上一篇文章的推理:

  1. 当函数不想修改形参且值很容易复制时(int、double、char、bool等),按值传递。简单类型。std::string, std::vector和所有其他STL容器都不是简单类型。)

  2. 当复制该值的代价太大且函数不想修改所指向的值且NULL是函数处理的有效预期值时,传递一个const指针。

  3. 当复制该值的开销太大,且函数想修改所指向的值,并且NULL是函数处理的有效预期值时,传递一个非const指针。

  4. 当复制该值的代价太大且函数不想修改引用的值时,通过const引用传递,如果使用指针则NULL将不是一个有效值。

  5. 当复制该值的代价太大且函数想要修改引用的值时,通过非连续引用传递,如果使用指针则NULL将不是一个有效值。

  6. 在编写模板函数时,没有一个明确的答案,因为需要考虑一些超出本文讨论范围的权衡,但可以说,大多数模板函数通过值或(const)引用获取形参,然而,因为迭代器语法类似于指针(星号表示“解引用”)。任何期望迭代器作为参数的模板函数默认也接受指针(并且不检查NULL,因为NULL迭代器的概念有不同的语法)。

http://www.cplusplus.com/articles/z6vU7k9E/

我从中得到的是,选择使用指针或引用参数的主要区别是NULL是否为可接受的值。就是这样。

不管这个值是输入的、输出的、可修改的等等,都应该在关于函数的文档/注释中。

这里的大多数答案都未能解决在函数签名中使用原始指针所固有的模糊性,就表达意图而言。问题如下:

  • 调用者不知道指针是指向单个对象,还是指向对象“数组”的开始。

  • 调用者不知道指针是否“拥有”它所指向的内存。IE的函数是否应该释放内存。(foo(new int) -这是内存泄漏吗?)

  • 调用者不知道nullptr是否可以安全地传递给函数。

这些问题都是通过参考文献来解决的:

  • 引用总是引用一个对象。

  • 引用从来不拥有它们所引用的内存,它们仅仅是对内存的一个视图。

  • 引用不能为空。

这使得参考资料更适合用于一般用途。然而,推荐信并不完美——有几个主要问题需要考虑。

  • 没有明确的间接性。对于原始指针,这不是问题,因为我们必须使用&操作符来表明我们确实传递了一个指针。例如,int a = 5; foo(a);这里根本不清楚a是通过引用传递的,并且可以修改。
  • Nullability。指针的这个缺点也可以是一个优点,当我们实际想要将引用设为可空时。鉴于std::optional<T&>是无效的(有很好的理由),指针给了我们你想要的可空性。

因此,当我们想要一个具有显式间接的可空引用时,我们应该使用T*,对吗?错了!

抽象

在我们迫切需要可空性的时候,我们可能会使用T*,并简单地忽略前面列出的所有缺点和语义歧义。相反,我们应该追求c++最擅长的东西:抽象。如果我们简单地编写一个包装指针的类,我们就获得了表示性、可空性和显式间接性。

template <typename T>
struct optional_ref {
optional_ref() : ptr(nullptr) {}
optional_ref(T* t) : ptr(t) {}
optional_ref(std::nullptr_t) : ptr(nullptr) {}


T& get() const {
return *ptr;
}


explicit operator bool() const {
return bool(ptr);
}


private:
T* ptr;
};
这是我能想到的最简单的界面,但它有效地完成了工作。 它允许初始化引用、检查值是否存在并访问该值。我们可以这样使用它:

void foo(optional_ref<int> x) {
if (x) {
auto y = x.get();
// use y here
}
}


int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability

我们已经实现了我们的目标!现在让我们看看与原始指针相比的好处。

  • 接口清楚地显示了引用应该只引用一个对象。
  • 显然,它并不拥有所引用的内存,因为它没有用户定义的析构函数,也没有删除内存的方法。
  • 调用者知道nullptr可以被传入,因为函数作者显式地要求optional_ref

从这里开始,我们可以让接口变得更复杂,比如添加相等操作符、一个单一的get_ormap接口、一个获取值或抛出异常的方法、constexpr支持。那可以由你来做。

总之,与其使用原始指针,不如考虑这些指针在代码中的实际含义,并利用标准库抽象或编写自己的抽象。这将极大地改进您的代码。