Const References to Pointers
Asked Answered
O

6

16

I came across some code that used a method like this:

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

Now, I can't see any meaningful difference between that and this:

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

But obviously someone went way out of their way to use such an odd type. Or perhaps a refactoring tool went horribly wrong.

Is there any good reason to keep that function signature? Some sort of corner case that behaves differently? I can't think of anything.

Oligocene answered 26/3, 2011 at 0:8 Comment(5)
Probably just a copy-paste from the signature of QList::append, which takes const T& because it's generic and for all it knows, T is expensive to copy. Edit: Heh, not literally a copy-paste because your code has T const& whereas the QT docs I just checked have const T&. But mentally a copy, since the meaning is the same.Mf
@Steve This isn't passing an object by reference, it is passing a pointer to an object. There will be no expensive copying. I can't think of anything either but possibly for the use of const iterators?Olnek
@Khaled: I know. I'm not saying that Item* might be expensive to copy. I'm saying that in QList, T might be expensive to copy. This programmer has (I suspect) copied some code that has concerns irrelevant to the specific case of Item*. Since there's nothing really wrong with passing a pointer by const reference, they may even have kept it like that deliberately so that it looks consistent with QList::append - of course to people unfamiliar with QList, this is incongruous since you wouldn't normally pass a pointer that way.Mf
That actually makes a lot of sense. If a refactoring tool inferred the type off usage, you'd probably end up with that.Oligocene
And btw in general there are functions for which it makes a difference - if addItem were to take the address of item and compare it with other addresses from elsewhere, then it matters whether you have a reference to whatever the caller passed as the argument, or a copy of it. Obviously in this case, QList::append doesn't do that. So it doesn't apply here, but it is a reason why you can't just go around blindly replacing all instances of T* const& with T* and expect never to break anything. Hence that refactor tool (if there was one) was right to leave it that way.Mf
T
12

The only difference is that in the first version you would not be allowed to change the value of the local item inside the function (you could still modify the Item it points to). Therefore if you wanted an Item* to hold a different value for some reason, you 'd be forced to use another local of type Item* and the function would consume an additional sizeof(intptr_t) bytes of stack space (boo hoo).

Not earthshaking, I know.

Thermocouple answered 26/3, 2011 at 0:20 Comment(4)
Thanks. That seems to be a good summary of the difference between the two.Oligocene
"the function would consume an additional sizeof(intptr_t) bytes of stack space" - if the compiler was sufficiently rubbish (or the control flow sufficiently unpredictable) not to realise that item can be zapped at the point where you stop using it, and start using your extra variable.Mf
why isn't there mention of the fact that the first item is a reference in this answer, only that it's a const? Isn't that also a differenceChatham
@Julius: Because it does not make any practical difference in this scenario. And there's no point in mentioning cosmetic differences.Thermocouple
S
2

It's a reference to a const pointer, meaning that you get a nickname for this pointer but you can't change the adress it points to.

A pointer copy is equivalent yes, if you make it const too.

I smell a historical sequence of changes that got the code to this point, certainly after several naming refactoring.

Samarskite answered 26/3, 2011 at 0:14 Comment(0)
O
1

The first declaration means "item is a constant pointer reference variable". You cant change the item to point to another data of type ITEM, and also it's a reference so it points to the pointer which is passed as an argument in the called function.

Pseudo code: Item* myItem = new Pen(); additem(myItem); first declaration makes sure that the additem function wont change the pen object since it is a constant reference also it wont copy the contents of myItem, it just points to the memory location of myItem. Whereas second declaration has an overhead of copying the content of myItem.

Orianna answered 11/7, 2013 at 7:28 Comment(0)
C
0

It is a mutable reference to a constant pointer to a mutable value.

The reference would allow you to modify whatever it is a reference of, but it just so happens the referred type is a constant pointer, so the value of the pointer can't be changed. The value pointed to by the pointer can be changed.

void* const &

  • Original Pointer: Immutable
  • Referenced Pointer: Immutable
  • Original Value: Mutable

Passing a constant pointer directly would be equivalent.

void* const

  • Original Pointer: Immutable
  • Copied Pointer: Immutable
  • Original Value: Mutable
Clingy answered 26/3, 2011 at 0:28 Comment(3)
There is no such thing as a mutable reference. References are const by default, that's why you cannot declare them const (because it would be redundant)Indictment
Incorrect. A mutable reference is a reference where by, through the reference, the value can be mutated. You are referring to reseating a reference, which is not possible in C++. Attempting to make the reference itself constant is redundant.Clingy
Sorry, but you wrote "mutable reference to a constant pointer". That just doesn't make any sense. After the definition in your comment you'd have to say "const reference to a pointer". Or you could correctly say "reference to a const pointer". But "mutable reference to a constant pointer" is just pure nonsense.Indictment
A
0

One additional significant difference that occurs to me is that the actual data that is passed on the stack. In the Item* version, an Item* is passed as an argument, needing one dereference to get at the Item value. However, since references are implemented in the final machine code by passing pointers, the Item* const& version actually passes an Item** as an argument, needing two dereferences to get at the Item value.

This also explains why you'd need extra stack space to get a modifiable Item* version - Your caller didn't actually give you an Item* on the stack (or in a register) that you can mess around with, you only have a Item** (or Item* const& in C++ land).

Arnettaarnette answered 29/1, 2014 at 20:32 Comment(0)
M
0

Might have been copied from a template.

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; }
};

I wanted to make a generic class to calculate hashes.

intHash: set the parameter as constant
structHash: changed the signature to reference
structPtrHash: the only way it would compile

I reached this questing looking for "reference to a pointer of a constant", which was not achieved in my code.

The top comments of this question:
What is the difference between const int*, const int * const, and int const *?

recommends:
Clockwise/Spiral Rule
cdecl.org

The correct pattern for my case:

class constStructPtrHash : hashBase<const structData*> {
    int hash( const structData* const& c ) override { /* c->a=1; ERROR */ return 1; }
};
Mars answered 8/11, 2022 at 4:39 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.