0

关于“何时使用引用以及何时使用指针?”有很多问题。他们让我有点困惑。我认为引用不会占用任何内存,因为它只是地址。

现在我做了一个简单的Date课程,向他们展示了代码审查社区。他们告诉我不要在以下示例中使用引用。但为什么?
有人告诉我,它将分配与指针相同的内存。这与我学到的相反。

class A{
int a;
public:
   void setA(const int& b) { a = b; } /* Bad! - But why?*/
};

class B{
int b;
public:
   void setB(int c) { b = c; } /* They told me to do this */
};

那么我什么时候在参数中使用引用或指针,什么时候只是一个简单的副本?如果没有我的示例中的引用,常量是否不必要?

4

5 回答 5

8

不保证是坏的。但在这种特定情况下是不必要的。

在许多(或大多数)上下文中,引用被实现为变相的指针。您的示例恰好是其中一种情况。假设函数没有内联,参数b将作为指针“在后台”实现。因此,您setA在第一个版本中真正传递的是指向 的指针int即提供对您的参数值的间接访问的东西。在第二个版本中,您传递了一个立即数int,即提供对您的参数值的直接访问的东西。

哪个更好,哪个更差?好吧,在许多情况下,指针的大小比 大int,这意味着第一个变体可能会传递更大量的数据。这可能被认为是“不好的”,但由于这两种数据类型通常都适合硬件字长,因此可能不会产生明显的差异,尤其是在 CPU 寄存器中传递参数时。

此外,为了读取b函数内部,您必须取消引用该伪装的指针。从性能的角度来看,这也是“不好的”。

这些是人们希望通过值传递任何小尺寸(小于或等于指针大小)的参数的形式原因。对于参数或更大的大小,通过 const 引用传递成为一个更好的主意(假设您没有明确要求副本)。

但是,在大多数情况下,一个简单的函数可能会被内联,这将完全消除两个变体之间的差异,无论您使用哪种参数类型。


const在第二个变体中不必要的问题是另一回事。在第一个变体中,有const两个重要目的:

1)它可以防止你修改参数值,从而保护实际参数不被修改。如果引用不是const,您将能够修改引用参数,从而修改参数。

2) 它允许您使用右值作为参数,例如 call some_obj.setA(5)。没有const这样的电话是不可能的。

在第二个版本中,这都不是问题。无需保护实际参数不被修改,因为参数是该参数的本地副本。无论您对参数做什么,实际参数都将保持不变。SetA并且无论是否声明参数,您都可以使用右值作为参数const

const出于这个原因,人们通常不会对按值传递的参数使用顶级限定符。但是如果你声明它const,它只会阻止你修改b函数内部的本地。有些人实际上喜欢这样,因为它强制执行适度流行的“不要修改原始参数值”约定,因此您有时可能会看到在参数声明中使用顶级const限定符。

于 2013-07-12T16:08:50.123 回答
4

如果您有像 a 这样的轻量级类型,int或者long您应该使用按值传递,因为使用引用不会产生额外成本。但是当你传递一些重类型时,你应该使用引用

于 2013-07-12T16:08:59.670 回答
1

我同意审稿人的观点。这就是为什么:

对小型简单类型的(常量或非常量)引用,例如int会更复杂(就指令数量而言)。这是因为调用代码必须将参数的地址传递到setA中,然后setA必须从存储在中的地址中取消引用内部值b。在 where bis a plain的情况下int,它只是复制值本身。所以在保存中至少有一个内存引用的步骤。这在大型程序的长时间运行中可能没有太大区别,但是如果您在执行此操作的任何地方都不断添加一个额外的循环,那么它很快就会明显变慢。

我看了一段类似这样的代码:

class X
{
   vector v;
  public: 
   ... 
   void find(int& index, int b); 
   ....
}

bool  X::find(int &index, int b)
{
   while(v[index] != b)
   {
       if (index == v.size()-1)
       {
           return false;
       }
       index++; 
   }
   return true;
}

将此代码重写为:

bool  X::find(int &index, int b)
{
   int i = index;
   while(v[i] != b)
   {
       if (i == v.size()-1)
       {
           index = i;
           return false;
       }
       i++; 
   }
   index = i; 
   return true;
}

意味着这个函数从一些调用find相当多的代码的总执行时间的大约 30% 到相同测试执行时间的大约 5%。因为编译器放入i了一个寄存器,并且只有在完成搜索时才更新参考值。

于 2013-07-12T16:16:21.350 回答
0

引用被实现为指针(这不是必需的,但我相信它是普遍正确的)。

因此,在您的第一个中,由于您只是传递一个“int”,因此将指针传递给该 int 将需要大约相同数量的空间来传递(相同或更多的寄存器,或相同或更多的堆栈空间,具体取决于您的架构),所以那里没有节省。另外,现在您必须取消引用该指针,这是一项额外的操作(并且几乎肯定会导致您进入内存,这可能与第二个无关,这取决于您的架构)。

现在,如果您传递的内容比 int 大得多,那么第一个可能会更好,因为您只传递了一个指针。[注意在某些情况下,即使对于非常大的对象,按值传递仍然可能有意义。这些情况通常是您计划创建自己的副本时。在这种情况下,最好让编译器进行复制,因为整体方法可能会提高它的优化能力。这些案例非常复杂,我的观点是,如果您提出这个问题,您应该在尝试解决它们之前更多地学习 C++。尽管它们确实使阅读变得有趣。]

于 2013-07-12T16:19:45.100 回答
0

将原语作为 const-reference 传递并不会为您节省任何东西。指针和 int 使用相同数量的内存。如果传递 const-reference,机器将不得不为指针分配内存并复制指针地址,这与分配和复制整数的成本相同。如果您的 Date 类使用单个 64 位整数(或双精度)来存储日期,那么您不需要使用 const-reference。但是,如果您的 Data 类变得更复杂并存储了额外的字段,那么通过 const 引用传递 Date 对象的成本应该低于通过值传递它的成本。

于 2013-07-12T16:23:28.520 回答