Odd C++ reference passing issue

Hello Everyone,

I am writing a very simple RPC library that uses ArduinoJSON and I have bumped into an odd C++ behavior. During the instantiation of my class I pass the bus line on which I intend to use the RPC (the first parameter):

ArduinoRPC RPC(Serial, "CONTROL_UNIT", 1024);

Inside the class the constructor passes the aBUS_Line parameter to the local field fBUS_Line in order to store it for class methods. But the assignment below does not pass the value, and the fBUS_Line remains NULL:

class ArduinoRPC
{
  protected:
        Stream& fBUS_Line;
        ...
        
  public:
        ArduinoRPC(Stream& aBUS_Line, String aLocalHostname, size_t aHeapSize);
        ...
};

ArduinoRPC::ArduinoRPC(Stream& aBUS_Line, String aLocalHostname, size_t aHeapSize)
{
 fBUS_Line = aBUS_Line;
 fLocalHostname = aLocalHostname;
 fHeapSize = aHeapSize;
}

I have tried it out with an int type, it works flawlessly. But when I tried out the same int example with gcc, it does not even compile:

#include <iostream>

class MyClass
{
 int& myValueField;
 
 public:
 MyClass(int& myValueArgument)
 {
 myValueField = myValueArgument;
 std::cout << myValueArgument;
 std::cout << myValueField;
 }
};

int main()
{
 int myValue = 675;
 MyClass MyObject(myValue);
 return 0;
}
gcc ReferencePassing.cpp -lstdc++
ReferencePassing.cpp: In constructor ‘MyClass::MyClass(int&)’:
ReferencePassing.cpp:8:3: error: uninitialized reference member in ‘int&’ [-fpermissive]
    8 |   MyClass(int& myValueArgument)
      |   ^~~~~~~
ReferencePassing.cpp:5:7: note: ‘int& MyClass::myValueField’ should be initialized
    5 |  int& myValueField;
      |       ^~~~~~~~~~~~

What am I doing wrong? Could somebody please explain?

Many thanks for the advice.

Here is my complete alpha stage code for the RPC library:

ArduinoRPC.h

#ifndef ArduinoRPC_H
#define ArduinoRPC_H

#include "Arduino.h"
#include <ArduinoJson.h>

#define LABEL_SENDER "SENDER"
#define LABEL_RECIPIENT "RECIPIENT"
#define LABEL_REQUEST "REQUEST"
#define LABEL_RESPONSE "RESPONSE"
#define LABEL_DEVICE "DEVICE"

typedef DynamicJsonDocument tJsonDocument;

class ArduinoRPC
{
  protected:
 Stream& fBUS_Line;
 String fLocalHostname;
 size_t fHeapSize;
 
 tJsonDocument CreateMessageHeader(String aRemoteHostname, String aMessageLabel, String aValue);
 tJsonDocument BuildResponse(String aRemoteHostname, String aResponse);
 public:
    ArduinoRPC(Stream& aBUS_Line, String aLocalHostname, size_t aHeapSize);
 tJsonDocument BuildRequest(String aRemoteHostname, String aRequest);
 size_t SendMessage(tJsonDocument aMessageJSON);
};

#endif

ArduinoRPC.cpp

#include "ArduinoRPC.h"

ArduinoRPC::ArduinoRPC(Stream& aBUS_Line, String aLocalHostname, size_t aHeapSize)
{
 fBUS_Line = aBUS_Line;
 fLocalHostname = aLocalHostname;
 fHeapSize = aHeapSize;
}

tJsonDocument ArduinoRPC::CreateMessageHeader(String aRemoteHostname, String aMessageLabel, String aValue)
{
  tJsonDocument lMessageJSON(fHeapSize);
  lMessageJSON[LABEL_SENDER] = fLocalHostname;
  lMessageJSON[LABEL_RECIPIENT] = aRemoteHostname;
  lMessageJSON[aMessageLabel] = aValue;
 return lMessageJSON;
}

tJsonDocument ArduinoRPC::BuildResponse(String aRemoteHostname, String aResponse)
{
  return CreateMessageHeader(aRemoteHostname, LABEL_RESPONSE, aResponse);
}

tJsonDocument ArduinoRPC::BuildRequest(String aRemoteHostname, String aRequest)
{
  return CreateMessageHeader(aRemoteHostname, LABEL_REQUEST, aRequest);
}

size_t ArduinoRPC::SendMessage(tJsonDocument aMessageJSON)
{
 /************************** IT CRASHES THE BOARD AND RESETS ***************************/
 return serializeJson(aMessageJSON, fBUS_Line);
 /************************** IT CRASHES THE BOARD AND RESETS ***************************/

 // This works
// return serializeJson(aMessageJSON, Serial);
}

The sketch:

#include <ArduinoJson.h>
#include <ArduinoRPC.h>

void setup()
{
  Serial.begin(57600);
  while (!Serial) continue;
  Serial.println("Initializing board");
  
  ArduinoRPC RPC(Serial, "CONTROL_UNIT", 1024);
  DynamicJsonDocument lMessageJSON = RPC.BuildRequest((String)"SensorArray", (String)"INIT");
  JsonArray lAttachment = lMessageJSON.createNestedArray("MyDevice");
  lAttachment.add(5);
  lAttachment.add(4);
  RPC.SendMessage(lMessageJSON);
}

void loop()
{

}

Sorry for the long post. :frowning:

The class constructors should not be inside the setup loop, but outside, like a type definition.

Perehama:
The class constructors should not be inside the setup loop, but outside, like a type definition.

That is how it is defined. See the complete code below.
I only put them in the same text window for readability.

your class is

class ArduinoRPC

your constructor is

ArduinoRPC(Stream& aBUS_Line, String aLocalHostname, size_t aHeapSize);

in your INO file, you have the following line within the braces of setup()

ArduinoRPC RPC(Serial, "CONTROL_UNIT", 1024);

Are you sure you want,

 Stream& fBUS_Line;

or,

 int& myValueField;

defined as a reference value.

Ok, let me extrapolate.

  1. The sketch I have posted above is only for testing out the reference-passing issue. It is not the actual code, where the instantiation is defined as a global variable. The real code contains references to many classes with many unrelated topics. So I have put together a small code that can demonstrate the problem. That is what I have posted above.

  2. I wanted to be certain that Serial.begin() has been called before passing it’s class’ instance to my class.

In other words, in this example below, the ArduinoRPC’s instantiation is called first, BEFORE the Serial.begin() would be called. Therefore, there is a concern that the “Serial” is passed to the ArduinoRPC instance before it has been properly initialized.

#include <ArduinoJson.h>
#include <ArduinoRPC.h>

ArduinoRPC RPC(Serial, "CONTROL_UNIT", 1024);

void setup()
{
  Serial.begin(57600);
  while (!Serial) continue;
  Serial.println("Initializing board");
 
  DynamicJsonDocument lMessageJSON = RPC.BuildRequest((String)"SensorArray", (String)"INIT");
  JsonArray lAttachment = lMessageJSON.createNestedArray("MyDevice");
  lAttachment.add(5);
  lAttachment.add(4);
  RPC.SendMessage(lMessageJSON);
}

void loop()
{

}

JimEli:
Are you sure you want,

 Stream& fBUS_Line;

or,

 int& myValueField;

defined as a reference value.

I want the Stream& fBUS_Line. And yes I want it be a reference as I don’t want to make a copy of the instance. I want to pass the original instance from the sketch.

So I could use any kind of BUSes. For example:

ArduinoRPC SerialRPC(Serial, "CONTROL_UNIT", 1024);
ArduinoRPC WireRPC(Wire, "CONTROL_UNIT", 1024);

The int& example was only for testing out the reference passing anomaly. Again, the conclusion is the int& works, the Stream& does not. What I don’t understand is why?

You have to use an initialization list to initialize references.

https://en.cppreference.com/w/cpp/language/constructor

Many thanks Whandal! It has solved the problem! It works like a charm now.