c++arduinopass-by-referencestdvector

Initializing a vector with a class that has a pass-by-reference constructor parameter and saves that reference as a member


I'm trying intending to create a single instance of a type that gets shared among the items in a vector. Each of the vector's item types requires a pass-by-reference parameter in the constructor but I can't seem to get the vector to initialize properly.

//The type of each item in the vector
class PollingConfig_t
{
    public: 
        PollingConfig_t(DebugPrinter_t& _debugPrinter) : _debugPrinter(debugPrinter) {}
    private:
        DebugPrinter_t& _debugPrinter; //Reference to the global instance of DebugPrinter_t
}

And for the actual globals:

DebugPrinter_t DebugPrinter(true); //One instance shared among vector items
std::vector<PollingConfig_t> PollingConfigs(4, PollingConfig_t(DebugPrinter)); //4 instances of PollingConfig_t, each with reference to DebugPrinter.

This doesn't compile and give me an error: error: use of deleted function 'PollingConfig_t& PollingConfig_t::operator=(const PollingConfig_t&)'

HOWEVER, I can get it work seemingly as intended if I simply remove the ampersand in the _debugPrinter declaration.

    private:
        DebugPrinter_t _debugPrinter;

But wouldn't this create copies of the original instance DebugPrinter for each of the vector's items? I mean if _debugPrinter is declared as a DebugPrinter_t instead of DebugPrinter_t& it's no longer a reference, right?


Solution

  • std::vector need to copy/move the elements in it (e.g when it is elarged and require reallocation).
    In your case this is a problem because your element contains a reference which once initialized cannot be changed.
    Adding operator= is not a good solution because you will still have a problem assigning the reference.
    And you are correct that having DebugPrinter_t _debugPrinter is also not good because it will cause copies to be made.

    You can solve it in various ways, e.g.:

    1. Use a pointer instead of a reference:

      class PollingConfig_t
      {
      public: 
          PollingConfig_t(DebugPrinter_t * pDebugPrinter) : m_pDebugPrinter(pDebugPrinter) {}
      private:
          DebugPrinter_t * m_pDebugPrinter; // pointer to the global instance of DebugPrinter_t
      }
      

      Then initialize the vector with:

      DebugPrinter_t DebugPrinter(true); //One instance shared among vector items
      std::vector<PollingConfig_t> PollingConfigs(4, PollingConfig_t(&DebugPrinter)); // NOTE the & to take the address of the global object to initialize the pointer
      

      And in order to use the global object from within PollingConfig_t, use: m_pDebugPrinter-> ....

    2. Use a std::shared_ptr:
      The std::shared_ptr support multile owners, and also takes care of the lifetime of the object.

      class PollingConfig_t
      {
      public: 
          PollingConfig_t(std::shared_ptr<DebugPrinter_t> pDebugPrinter) : m_pDebugPrinter(pDebugPrinter) {}
      private:
          std::shared_ptr<DebugPrinter_t> m_pDebugPrinter; // shared_ptr to the global instance of DebugPrinter_t
      }
      

      You need to change the creation of the global object and the vector as following (note the usage of std::make_shared for creating the global object):

      std::shared_ptr<DebugPrinter_t> DebugPrinter = std::make_shared<DebugPrinter_t>(true); //One instance shared among vector items
      std::vector<PollingConfig_t> PollingConfigs(4, PollingConfig_t(DebugPrinter)); // NOTE the shared_ptr is passed by value. It keeps a reference count on the object.
      

      Using the global object from within PollingConfig_t is the same as with a raw pointer: m_pDebugPrinter-> ....

    3. Use a singleton for the DebugPrinter_t object:
      Since you have only one instance shared by all entities, you can use the signleton design pattern.
      This way you don't even need to add a member to each PollingConfig_t because all element will have access to the singleton.
      I mentioned this the last since it is somewhat discouraged.
      But since you have a global object anyway (which is the common claim against singletons) it might fit.