15

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.

cgmb
  • 4,284
  • 3
  • 33
  • 60
  • 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. – Steve Jessop Mar 26 '11 at 00:11
  • @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? – Khaled Nassar Mar 26 '11 at 00:15
  • @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. – Steve Jessop Mar 26 '11 at 00:17
  • 1
    That actually makes a lot of sense. If a refactoring tool inferred the type off usage, you'd probably end up with that. – cgmb Mar 26 '11 at 00:19
  • 1
    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. – Steve Jessop Mar 26 '11 at 00:24

6 Answers6

10

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.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • Thanks. That seems to be a good summary of the difference between the two. – cgmb Mar 26 '11 at 00:25
  • "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. – Steve Jessop Mar 26 '11 at 00:27
  • 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 difference – Jules G.M. Jul 22 '14 at 11:27
  • @Julius: Because it does not make any practical difference in this scenario. And there's no point in mentioning cosmetic differences. – Jon Jul 22 '14 at 12:47
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.

Klaim
  • 67,274
  • 36
  • 133
  • 188
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.

Raju
  • 1,149
  • 1
  • 6
  • 19
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).

Matthijs Kooijman
  • 2,498
  • 23
  • 30
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
Sion Sheevok
  • 4,057
  • 2
  • 21
  • 37
  • 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) – Paul Groke Mar 26 '11 at 04:24
  • 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. – Sion Sheevok Mar 29 '11 at 16:56
  • 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. – Paul Groke Mar 29 '11 at 23:44
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; }
};
Ian
  • 679
  • 6
  • 10