The Basics of Class Data Members

In this section, we'll touch on class data members. You'll see how Modern C++ can simplify syntax and make code safer.

We'll cover the following

Basics

Defining a class or a struct in C++ allows you to model your domain and solve problems more naturally. C++ provides a set of built-in types, including Boolean, integral, character, and floating-point. Additionally, you can use objects from the Standard Library, like std::string, std::vector, std::optional and many others. You can wrap these essential components and build your objects.

To create a background for our mini course, let’s create a data structure that represents a Car:

Here’s a basic code for that Car class:

#include <iostream>
#include <string>
struct Car {
int seats;
double maxVelocity;
std::string name;
};
int main() {
Car oldCar;
oldCar.seats = 3;
oldCar.maxVelocity = 42.5;
oldCar.name = "super fast but old";
std::cout << "seats: " << oldCar.seats << '\n';
std::cout << "max velocity: " << oldCar.maxVelocity << '\n';
std::cout << "name: " << oldCar.name << '\n';
}

In the above example, we defined a simple structure that holds data for a Car. By default, struct has public access to its members. This way, we can easily change their values directly.

Here’s a corresponding example where we define a class with getters and setters.

#include <iostream>
#include <string>
class Car {
public:
// other interface...
// getters and setters:
int GetSeats() const { return seats_; }
double GetMaxVelocity() const { return maxVelocity_; }
std::string GetName() const { return name_; }
void SetSeats(int val) { seats_ = val; }
void SetMaxVelocity(double val) { maxVelocity_ = val; }
void SetName(const std::string& val) { name_ = val; }
private:
int seats_;
double maxVelocity_;
std::string name_;
};
int main() {
Car oldCar;
oldCar.SetSeats(3);
oldCar.SetMaxVelocity(42.5);
oldCar.SetName("super fast but old");
std::cout << "seats: " << oldCar.GetSeats() << '\n';
std::cout << "velocity: " << oldCar.GetMaxVelocity() << '\n';
std::cout << "name: " << oldCar.GetName() << '\n';
}

Above, you can see almost the same type of declaration, but now it’s a class. Usually, in C++, we use classes to define types that are “larger” than those with struct. This means that there might be some extra API and member functions.

Unfortunately, the code is not perfect. Let’s think about how to improve it and build a correct class/struct definition.

Issues

The main problem with our code is that it’s very unfriendly, and we have to “manually” initialize all the variables once the object is created. We do this through direct member access:

Car oldCar;
oldCar.seats = 3; 
oldCar.maxVelocity = 42.5;
oldCar.name = "super fast but old";

Or by calling setters:

Car oldCar;
oldCar.SetSeats(3); 
oldCar.SetMaxVelocity(42.5);
oldCar.SetName("super fast but old");

At this point, we need to ask two questions:

  • How to take care of default value initialization for our data members. We don’t want to leave them initialized because it might lead to some undefined behavior and bugs.
  • How to provide a handy protocol to initialize data members during object creation.

Solutions

Fortunately, with C++, we can leverage a few techniques to solve issues with data member initialization.

First, we can use different flavors of constructors and use simpler way to assign values to data members.

Secondly, we can apply non-static data member initialization (NSDMI) to default-initialize members.