指向常量的指针引用

15

我遇到了一些使用类似这样的方法的代码:

QList<Item*> itemList;
void addItem(Item* const& item)
{
    itemList.append(item);
}

现在,我无法看出它和这个之间有任何实质性的区别:

QList<Item*> itemList;
void addItem(Item* item)
{
   itemList.append(item);
}

很明显,有人费尽心思使用了这样奇怪的类型。或者说是重构工具出了巨大的岔子。

是否有任何好的理由保留那个函数签名?有些特殊情况会产生不同的行为吗?我想不出来。


5
可能只是从QList :: append的签名中复制粘贴而来,该函数使用const T&是因为它是通用的,对于所有类型 T,都可能很昂贵。编辑:嘿,代码中有T const&,而我刚才查看的QT文档中有const T&,但从意义上讲它们是相同的,所以可以认为是一种复制。 - Steve Jessop
@Steve 这不是通过引用传递对象,而是传递对象的指针。这样就不会有昂贵的复制操作。 我也想不到其他情况了,可能只有使用const迭代器时需要注意? - Khaled Nassar
@Khaled:我知道。我不是说Item*复制起来可能很昂贵。我是说在QList中,T可能很昂贵。这位程序员(我怀疑)复制了一些与Item*特定情况无关的代码。由于通过const引用传递指针没有什么问题,他们甚至可能故意保持这种方式,以便看起来与QList::append一致 - 当然对于不熟悉QList的人来说,这是不协调的,因为你通常不会以这种方式传递指针。 - Steve Jessop
1
这其实很有道理。如果重构工具能够推断出使用的类型,你最终可能会得到那个结果。 - cgmb
1
顺便说一下,通常有些函数会有所区别——如果addItem需要获取item的地址并将其与其他地方的地址进行比较,则无论您是否拥有对调用方传递的参数的引用或副本都很重要。显然,在这种情况下,QList::append不会这样做。因此,它在这里不适用,但这是您不能盲目地替换所有T* const&实例为T*并期望永远不会出现问题的原因之一。因此,那个重构工具(如果有的话)保留了它的方式是正确的。 - Steve Jessop
6个回答

11
唯一的区别是,在第一个版本中,您将不被允许在函数内更改本地 item 的值(您仍然可以修改其指向的 Item )。因此,如果由于某种原因要求 Item * 持有不同的值,则必须使用另一个本地变量(类型为 Item * ),并且该函数将消耗额外的 sizeof(intptr_t)字节的堆栈空间(唉)。
不会震惊世界,我知道。

谢谢。这似乎是两者之间差异的一个很好的总结。 - cgmb
如果编译器不够好(或者控制流程不够可预测),没有意识到在你停止使用item并开始使用额外变量的时候,item可能已经被清除,那么该函数将会消耗额外的sizeof(intptr_t)字节的堆栈空间。 - Steve Jessop
为什么这个答案没有提到第一个项目是一个引用,只提到它是一个const?这不也是一个区别吗? - Jules G.M.
@Julius:因为在这种情况下它并没有实际的影响。而且提到外观上的差异也没有意义。 - Jon

2

这是一个指向常量的指针引用,意味着你为该指针取了一个昵称,但不能改变它所指向的地址。

如果将指针复制并将其也设为const,则等效于指针引用。

我感觉这段代码经历了一系列历史性的更改,肯定经过了多次重命名。


1
第一个声明表示“item是一个常量指针引用变量”。您不能将item更改为指向另一种类型ITEM的数据,它也是一个引用,因此它指向传递给被调用函数的指针。
伪代码:Item * myItem = new Pen(); additem(myItem); 第一个声明确保additem函数不会更改笔对象,因为它是一个常量引用,也不会复制myItem的内容,它只指向myItem的内存位置。而第二个声明有复制myItem内容的开销。

0

可能是从模板复制的。

template<class T>
class hashBase {
    virtual int hash( const T& c ) = 0;
    // other stuff: save hash, compare, etc.
};

class intHash : hashBase<int> {
    int hash( const int& c ) override { /* c=1; ERROR */ return 1; }
};

struct structData{ int a; };
class structHash : hashBase<structData> {
    int hash( const structData& c ) override { /* c.a=1; ERROR */ return 1; }
};

class structPtrHash : hashBase<structData*> {
    int hash( structData* const& c ) override { c->a=1; return 1; }
};

我想制作一个通用的类来计算哈希值。

intHash:将参数设置为常量
structHash:更改签名以引用
structPtrHash:这是唯一能编译的方式

我在寻找“指向常量指针”的引用时遇到了这个问题,但在我的代码中没有实现。

这个问题的顶部评论:
const int*、const int * const和int const *之间有什么区别?

推荐:
顺时针/螺旋规则
cdecl.org

对于我的情况,正确的模式是:

class constStructPtrHash : hashBase<const structData*> {
    int hash( const structData* const& c ) override { /* c->a=1; ERROR */ return 1; }
};

0

这是一个可变的常量指针的可变引用。

引用允许您修改其所引用的任何内容,但恰好被引用的类型是一个常量指针,因此指针的值不能被更改。指针所指向的值可以被更改。

void* const &

  • 原始指针:不可变
  • 引用指针:不可变
  • 原始值:可变

直接传递一个常量指针将是等效的。

void* const

  • 原始指针:不可变
  • 复制指针:不可变
  • 原始值:可变

可变引用并不存在。引用默认是const的,这就是为什么你不能声明它们为const(因为这是多余的)的原因。 - Paul Groke
不正确。可变引用是通过引用可以改变值的引用。您正在引用重新分配引用,这在 C++ 中是不可能的。尝试使引用本身恒定是多余的。 - Sion Sheevok
抱歉,但你写的“可变引用常量指针”这个概念是没有意义的。根据你在评论中的定义,你应该说“指向常量的引用”,或者正确地说“引用常量指针”。但“可变引用常量指针”这个说法纯属胡言乱语。 - Paul Groke

0

我想到的另一个重要区别是传递到堆栈上的实际数据。在 Item* 版本中,需要将 Item* 作为参数传递,需要进行一次解引用才能获取 Item 值。然而,由于引用在最终机器代码中是通过传递指针来实现的,因此 Item* const& 版本实际上会传递一个 Item** 作为参数,需要进行两次解引用才能获取 Item 值。

这也解释了为什么你需要额外的堆栈空间来获取可修改的 Item* 版本 - 调用者实际上没有在堆栈(或寄存器)上给你一个可以随意操作的 Item*,你只有一个 Item**(或在 C++ 中是 Item* const&)。


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