0

Consider the below example where I create the local variable specialNumber in main(), and pass it by reference to a new thread, as well as another function (please disregard the lack of a lock/mutex):

#include <iostream>
#include <thread>

void threadRun(int& number) {
    while(true) {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        std::cout << number << std::endl;
        number += 1;
    }
}

int main() {
    int specialNumber = 5;
    std::thread newThread(threadRun, std::ref(specialNumber));
    otherFunction(specialNumber);
    newThread.join();
}


void otherFunction(int& number) {
    // does something with number
}

I am aware that passing references to local variables around should generally be avoided, as once that function terminates the variable will be out of scope and the reference will be invalid.

However, since the variable is local to main(), and that function won't terminate until the whole program terminates, is there anything wrong with this practice?

My specific use-case would be storing a small object here (mainly consisting of pointers to heap objects along with helper functions), which would be used by multiple threads and/or functions, and passing around a reference to it. I'm aware an alternative is storing it in the heap with a smart pointer such as shared_ptr, but storing such a small object this way seems inefficient to me.

I apologize if any of my terminology is incorrect, I am quite new to C++. Please do correct me!

gaskini
  • 65
  • 4
  • 1
    you need synchronization to avoid a data-race, but thats not special for passing a reference, would be the same if you used eg a smart pointer – 463035818_is_not_an_ai Mar 21 '20 at 19:06
  • 1
    The way you're using it here is bad practice, because you're not synchronizing access to it between threads. What you've posted is the opposite of being thread-safe. – Ken White Mar 21 '20 at 19:08
  • @KenWhite At the top of the post I put "please disregard the lack of a lock/mutex". I'm aware this isn't thread safe, but that isn't what the question was about. I'm specifically asking about the practice of passing around a reference to a local variable in the main function, you can assume this example is thread safe. – gaskini Mar 21 '20 at 19:13
  • In this specific case smart pointers are overkill (and would do you no good anyway). It's a question of (a) ownership and (b) lifetime. The answer to (a) generally gets to decide the state of (b). That goes for *both* the data *and* the thread itself. In this case (a) is `main()`, and controls the (b) of both the thread *and* the data passed, so a reference is adequate. You're much bigger problem is completely inadequate synchronization to your shared data. But that doesn't appear to be your question. – WhozCraig Mar 21 '20 at 19:18

2 Answers2

6

Your assumption

I am aware that passing references to local variables around should generally be avoided

seems unfounded.

There is nothing wrong with passing references to functions. However, a function that takes a reference to an object should not take ownership of that object. The function should not assume that the referenced object continues to live after it exits.

This is different from returning references to local variables, which is always wrong.

I see no problem (missing synchronization aside) with passing references to threads and this generally preferable to the alternative of using global variables.

Smart pointers, such as std::shared_ptr, are only required if the functions are supposed to take (shared) ownership of the object, e.g. if threadRun wants to keep a reference/pointer to the object after it exits.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • You're right, I was definitely getting confused between returning references to local variables vs passing them to other functions. Thank you very much! – gaskini Mar 21 '20 at 19:22
0

As long as the main thread is living you shouldn't see a problem occurring since the lifetime of specialNumber is controlled by it.

However I want to elaborate on the use of std::ref(). One of the uses of std::ref() is precisely the scenario you are coding.

When you use std::ref() you are actually returning a std::reference_wrapper which can be copied. Reference wrappers can be stored in containers whereas plain references cannot.

Passing objects by reference to the constructor of a thread is one of the ways in which a reference wrapper comes in useful and std::ref() returns a reference wrapper.

Had you passed a simple reference you would see different behavior.

Read more about std::ref() and std::reference_wrapper

This thread How is tr1::reference_wrapper useful? is also helpful.

pcodex
  • 1,812
  • 15
  • 16