1

In QT, I am using a QAction object to enable/disable menu buttons when the user clicks them. It occurred to me that I was writing the same functionality for every case so I turned it into a function with a private hash table that would do all of the controlling of the enabling and disabling of the menu buttons. In other words, my hash table looks like std::unordered_map<bool,QAction*> table. Where the boolean is the key and the object is the value I am updating. So I wrote the following function:

void updateButton(QAction *currentButton)
{
  if(!table.empty())
  {
    // Enable the last menu item you clicked on and disable the current menu item.
    table[false]->setEnabled(true);
    currentButton->setEnabled(false);
    table[false] = currentButton;
  }
  else
  {
    // Menu item was clicked for the first time
    currentButton->setEnabled(false);
    table[false] = currentButton;
  }
} 

So whenever I clicked on a menu item. I would call this function at the very top:

void on_action_menuItem1_triggered()
{
  updateButton(ui->actionDoAction);

  ...
}

I then realized I was passing the pointer by value. And since I have so many buttons I have to manage I don't want to be making any copies if I can avoid it. At this point I totally forgot I could do QAction *&currentButton to pass the pointer by reference. So I started looking around and I found std::reference_wrapper. So I then changed the function to:

void updateButton(std::reference_wrapper<QAction*> currentButton)
{
   ...
}

and calling it by:

updateButton(std::ref(ui->actionDoAction));

Is there any benefit to doing it this way rather than QAction *&currentButton?

9
  • Pointers are small objects, cheap to copy, you might pass them by value. Commented Mar 21, 2019 at 18:20
  • std::reference_wrapper under the wood is a pointer too. Commented Mar 21, 2019 at 18:21
  • 1
    @Sailanarmo Passing a pointer by value does not copy the object, it only copies the pointer. Commented Mar 21, 2019 at 18:26
  • 1
    "I am modifying a state in the pointer" - I doubt that. You may be modifying something the pointer points to, but I would be very surprised if you were modifying parts of the pointer variable itself. Please be specific and accurate in the words you use to describe your actions. Commented Mar 21, 2019 at 18:40
  • 1
    @Sailanarmo the only reason to pass the pointer by reference would be if you wanted to modify the pointer itself (what it points to, in the caller). So no. Commented Mar 21, 2019 at 18:55

3 Answers 3

2

Short Answer

There is no benefit to passing by reference-to-pointer instead of by pointer unless you wish to modify the original pointer itself.

Long Answer

Based on the way your question is framed, as well as your first reply to comments to your question, you have a fundamental misunderstanding of what pointers and references actually are. Contrary to what you seem to believe, they are not the objects themselves.

Lets go back to the fundamentals a bit.

There are two main areas in memory we have access to in order to store data, the Stack memory and the Heap memory.

When we declare variables, we get allocated space in Stack memory and we can access the data by using the variable names. The memory gets automatically de-allocated when the variable falls out of scope.

void myFunction() {
    MyClass instance;         // allocated in Stack memory
    instance.doSomething();

}   // instance gets automatically de-allocated here

The biggest problem is that the amount of Stack memory is extremely limited compared to normal program needs and the fact that you often need data to persist outside certain scopes that creating instances of large classes in Stack memory is usually a bad idea. That's where the Heap becomes useful.

Unfortunately, with Heap memory, you need to take over the lifetime of the memory allocation. You also don't have direct access to data in Heap memory, you need a kind of stepping-stone to get there. You must explicitly ask the OS for a memory allocation, and then explicitly tell it to de-allocate the memory when you're done. C++ give us two operators for this: new and delete.

void myFunction(){
   MyClass *instance = new MyClass();  // ask OS for memory from heap
   instance->doSomething();
   delete instance;                    // tell OS that you don't need the heap memory anymore  
}

As you clearly seem to understand, in this case instance is known as a pointer. What you don't seem to realise is that a pointer is not an instance of an object itself, it is the "stepping stone" TO the object. The purpose of a pointer is to hold that memory address so we don't lose it and enable us to get to that memory by de-referencing the memory location.

In C++ there are two ways of doing this: you either de-reference the entire pointer, then access the object's members just like you would an object on the Stack; or, you would use the Member Dereferencing operator and access the member using that.

void myFunction(){
    MyClass *instance = new MyClass();
    (*instance).doSomething();          // older-style dereference-then-member-access
    instance->doSomethingElse();        // newer-style using member dereference operator
}

Pointers themselves are merely special-instances of integers. The values they contain are the memory addresses in the Heap memory where you allocate your objects. Their size depends on the platform you have compiled for (usually 32-bit or 64-bit) so passing them around is nothing more expensive than passing an integer around.

I cannot stress it enough that pointer variables are not the objects themselves, they are allocated in Stack memory and behave exactly like any other stack variable when they go out of scope.

void myFunction() {
    MyClass *instance = new MyClass();         // pointer-sized integer of type 'pointer-to-MyClass' created in Stack memory
    instance->doSomething();
}  // instance is automatically de-allocated when going out of scope. 
// Oops! We didn't explicitly de-allocate the object that 'instance' was pointing to 
// so we've lost knowledge of it's memory location. It is still allocated in Heap 
// memory but we have no idea where anymore so that memory is now 'leaked'

Now, because under-the-hood, pointers are nothing more than special-purpose integers, passing them around as no more expensive than passing any other kind of integer.

void myFunction(){
    MyClass *instance = new MyClass();  // 'instance' is allocated on the Stack, and assigned memory location of new Heap allocation
    instance->doSomething();
    AnotherFunction(instance);
    delete instance;                    // Heap memory pointed to is explicitly de-allocated
} // 'instance' is automatically de-allocated on Stack

void anotherFunction(MyClass *inst){ // 'inst' is a new pointer-to-MyClass on the Stack with a copy of the memory location passed in
    inst->doSomethingElse();
} // 'inst' is automatically de-allocted

So far, I have not mentioned references, because they are by-and-large the same as pointers. They are also just integers under-the-hood but they simplify usage by making the member-access syntax the same as that of Stack variables. Unlike ordinary pointers, references have to be initialised with valid memory location and that location cannot be changed.

The following are functionally equivalent:

MyClass &instance
MyClass * const instance

References to pointers are double-indirections, they're essentially pointers to pointers and are useful if you want to be able to manipulate, not only the Heap object, but also the pointer containing the memory location to that heap object.

void myFunction(){
    QString *str = new QString("First string");  // str is allocated in Stack memory and assigned the memory location to a new QString object allocated in Heap memory
    substituteString(str);                       
    delete str;                                  // de-allocate the memory of QString("Second String"). 'str' now points to an invalid memory location
} // str is de-allocated automatically from Stack memory

void substituteString(QString *&externalString){  // 'externalString' is allocated in Stack memory and contains memory location of 'str' from MyFunction()
   delete externalString;                        // de-allocate the Heap memory of QString("First string"). 'str' now points to an invalid location
   externalString = new QString("Second string"); // Allocate new Heap memory for a new QString object. 'str' in MyFunction() now points to this new location
} // externalString is de-allocated automatically from Stack memory

If I have explained myself clearly, and you have followed me so far, you should now understand that, in your case, when you pass a pointer to QAction to a function, you're not copying the QAction object, you're only copying the pointer to that memory location. Since pointers are merely integers under the hood, you're only copying something that's either 32-bits or 64-bits in size (depending on your project settings), and changing that to a reference-to-pointer will make absolutely no difference.

Sign up to request clarification or add additional context in comments.

1 Comment

This by far was the most helpful answer and clearly explained everything I was confused about.
0

Your question has a few different parts, and I'll address each of them separately.

How expensive is passing a pointer?

Passing a pointer is basically free. If the function you're passing it to doesn't have very many parameters, then the compiler will pass the pointer in a CPU register, meaning that the value of the pointer never even has to be copied onto the stack.

How expensive is passing by reference?

Passing something by reference has a few benefits.

  • A reference allows you to assume the reference points to a valid object (so you don't have to check if the address of the reference is null).
  • When you pass by reference, you can use it like it were a regular object; no need to use the -> syntax.
  • The compiler can (sometimes) generate a faster program because it knows that the reference can't be reassigned.

However, under the hood a reference is passed the same way that a pointer is. Both are just addresses. And both are basically free.

Which one should you use?

  • If you're passing a large object that you don't need to modify, pass it as a const reference.
  • If you're passing a small object that you don't need to modify, pass it by value
  • If you're passing a reference that you do need to modify, pass it by reference.

What are you modifying? (The button, or the pointer to the button?)

If you want to modify the button, pass the QAction by reference:

void pushButton(QAction& currentButton) {
    currentButton.push(); 
}

If you want to modify a pointer to the button, pass the pointer by reference. This is something of an unusual case, and I don't understand why you'd want to modify the pointer to the button.

void modifyPointer(QAction*& buttonPointer) {
    buttonPointer++; //I dunno, increment the pointer
}

Better yet, just return the modified pointer:

QAction* modifyPointer(QAction* buttonPointer) {
    return buttonPointer + 1;
}

Comments

0

If you are passing pointers as arguments, in almost all cases you'll want to pass them by value. The performance will be identical, as every major c++ compiler internally treats references as pointers anyway.

Or are you saying that you think an OBJECT COPY is created when you pass a pointer by value? That is not so - you can pass a pointer around as much as you want, no new objects will be allocated.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.