Member functions of a class

I am trying to acquaint myself with the use/abuse of classes and have hit a snag

Here is my test sketch

class train
{
  private:
    char *_name;

  public:
    train()  //constructor - no return type
    {
    }

    void setName(char *name)
    {
        strcpy(_name, name);  //save the name
    }

    char *getName()
    {
        return _name;
    }
};

train redTrain();  //instance of train object named redTrain

void setup()
{
    Serial.begin(115200);
    redTrain.setName("red");                          //give redTrain a name
    Serial.printf("Name : %s\n", redTrain.getName()); //print the name of redTrain
}

void loop()
{
}

This produces errors as follows

C:\Users\micro\Documents\Arduino\train_class_1\train_class_1.ino: In function 'void setup()':
C:\Users\micro\Documents\Arduino\train_class_1\train_class_1.ino:27:14: error: request for member 'setName' in 'redTrain', which is of non-class type 'train()'
   27 | 
      |              ^      
C:\Users\micro\Documents\Arduino\train_class_1\train_class_1.ino:28:43: error: request for member 'getName' in 'redTrain', which is of non-class type 'train()'
   28 |     redTrain.init("red");
      |                                           ^      

exit status 1

Compilation error: request for member 'setName' in 'redTrain', which is of non-class type 'train()'

This is using IDE 2.3.3 on Windows 11 with EP32 board files version 3.05 and ESP32 Dev Module as the target board. Reverting to earlier board files makes no difference

So, where have I gone wrong ?

Not an answer to your problem. But this does not reserve space to store the name. So you cannot strcopy something in this place.
You either need to hand a pointer to a piece of memory that will exist throughout the lifetime of your object, or you need to new() space, or you need to reserve a fixed char array.

"red" is a string literal. Not a char*.
Iso c++ somenumber forbids handing a pointer to a string literal...
It might or might not work, depending on your particular board architecture...

All above does not answer your question.
So now I get to the more desperate options...

Is train() a reserved function??? It appears in greenish color on my phone...
Did you try to rename your class?

Got it working. The error message was misleading because the cause of the problem was the misuse of the char *

class train
{
  private:
    char _name[20];

  public:
    train()  //constructor - no return type
    {
    }

    void setName(char name[20])
    {
        strcpy(_name, name);  //save the name
    }

    char *getName()
    {
        return _name;
    }
};

train redTrain;  //instance of train object named redTrain

void setup()
{
    Serial.begin(115200);
    redTrain.setName((char *)"red");                   //give redTrain a name
    Serial.printf("Name : %s\n", redTrain.getName());  //print the name of redTrain
    redTrain.setName((char *)"now RED");               //give redTrain a name
    Serial.printf("Name : %s\n", redTrain.getName());  //print the name of redTrain
}

void loop()
{
}

or the alternative version

class train
{
  private:
    char _name[20];

  public:
    train()  //constructor - no return type
    {
    }

    void setName(char name[20])
    {
        strcpy(_name, name);  //save the name
    }

    char *getName()
    {
        return _name;
    }
};

train redTrain;  //instance of train object named redTrain

void setup()
{
    Serial.begin(115200);
    char name[20];
    strcpy(name, "name 1");
    redTrain.setName(name);                            //give redTrain a name
    Serial.printf("Name : %s\n", redTrain.getName());  //print the name of redTrain
    strcpy(name, "name 2");
    redTrain.setName(name);                            //give redTrain a name
    Serial.printf("Name : %s\n", redTrain.getName());  //print the name of redTrain
}

void loop()
{
}

Thanks for the pointers (pun intended)

Your setName() function doesn't need to modify the source string. So, promise the compiler that you won't try to. Then get rid of the casts when you call it:

class train {
  private:
    char _name[20];

  public:
    train() {} //constructor - no return type

    void setName(const char *name) {
      strncpy(_name, name, 19); //save the name
      _name[19] = '\0';
    }

    char *getName(){
      return _name;
    }
};

train redTrain;  //instance of train object named redTrain

void setup() {
  Serial.begin(115200);
  redTrain.setName("red");                   //give redTrain a name
  Serial.printf("Name : %s\n", redTrain.getName());  //print the name of redTrain
  redTrain.setName("now RED");               //give redTrain a name
  Serial.printf("Name : %s\n", redTrain.getName());  //print the name of redTrain
}

void loop() {
}

This allows change of the private variable from outside... (violates the private label).
Should be:
char const * const

if you decide tou want to keep a local copy instead than a pointer to the original cString , I would recommend to use strncpy() or strlcpy() to not overflow your buffer size.

change

into

        strlcpy(_name, name, sizeof _name);  //save the name

Also I would advise against using _name for members. A frequent C++ naming convention to adopt is to use m_xxx for member variables but it diverges from the camelCase we are used to in Ardino's world so my recommendation would be to juste go for name and drop the underscore altogether.

Also I would capitalise the class name (Train instead of train)

As you constructor does not do anything, just don't write it. The C++ language guarantees that you'll get an implicit default constructor doing what you need (or alternatively pass the name to the constructor).

class Train {
  private:
    const char * name;
  public:
    Train(const char * name) : name(name) {}// constructor with initializer list
    const char *getName() {
      return name;
    }
};

Train trains[] = {"red", "blue", "black"};
const byte trainCnt = sizeof trains / sizeof * trains;

void setup() {
  Serial.begin(115200);

  // range for
  for (auto &aTrain : trains) {
    Serial.println(aTrain.getName());
  }

  // or traditional for with an index
  for (byte i = 0; i < trainCnt; i++) {
    Serial.print("Train #");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(trains[i].getName());
  }

}

void loop() {}

I wasn't yet addressing that part of the code, just the cause of the error message. But yea, it should be const char *getName() {. The other const is not needed as the pointer itself is passed by value.

obviously you can't copy a string to a point. perhaps a better alternative is to define a ptr, but malloc space once you know the size of the string

Thanks for all the tips

I have moved the sketch on quite a bit since getting past the original error, to the extent that I now have an array of Train (uppercase T @J-M-L ) objects each with several more member variables

Is there a specific reason for this ?
Some time ago when I first tinkered with classes the advice was to give private variables names preceded with an underscore, not because of any magic that it endowed them with, but as a reminder to the programmer that they were private member variables

How so ?

Done that, but it may confuse me later !

Because your original getName() returned a pointer to name. That returned pointer could be used to modify name without the class's knowledge. Per Post #9, return a const char *,

Then you might as well use a String class object rather than allocating the storage yourself. That guarantees that the storage will be freed when the containing object is destroyed, even if the author forgets to do so in their class's destructor.

In C++, identifiers starting with an underscore followed by an uppercase letter or containing double underscores are reserved for the implementation and should not be used by programmers.

Identifiers that start with an underscore followed by a lowercase letter are reserved at the global scope.

A member variable of your class is in the local scope, so the rule does not apply, but to avoid confusion and potential conflicts (if inadvertently used outside local scope), many C++ style guides suggest avoiding leading underscores entirely and you'll often see m_xxx being proposed if you want a special naming convention.

:wink:

you can write a constructor without parameters and one with the name

    Train() : name(nullptr) {}
    Train(const char * name) : name(name) {}

You could also write one with a parameter with a default value so that you could also call the constructor without any parameter

    Train(const char * name = nullptr) : name(name) {}

our style required the use of the underscore prefixes for static variables and function names within a file (not a class). such symbols would not be accessible outside file scope

i think a common motivations for underscore in classes is initialization in a constructor with similar names

ClassName (int x)
{
    _x = x;
}

which can be avoided

this->x = x;

aren't Strings prone to problems in Arduino?

my guess was that the instance of the class would more/less be permanent. But couldn't a destructor method handle freeing the allocated string.

or just

ClassName (int x) : x(x) {}

No more so than allocating dynamic storage yourself (and possibly forgetting to free it at the correct time).

Not if it's a local variable in a function.

Yes, as I mentioned, if the author remembers to do so. Given that a debugged / tested class already exists to handle dynamic allocation / destruction of character strings, it's safer to use it.

please explain this? what if there were multiple arguement values

It's an initializer list.

They would be comma-separated:

ClassName (int x, float y, char z) : x(x), y(y), z(z) {}

Note that the constructor's formal parameters could have different names than the member variables they initialize.