While C++ provides a number of fundamental data types (e.g. char, int, long, float, double, etc…) that are often sufficient for solving relatively simple problems, it can be difficult to solve complex problems using just these types. One of C++’s more useful features is the ability to define your own data types that better correspond to the problem being solved. You have already seen how enumerated types and structs can be used to create your own custom data types.
Here is an example of a struct used to hold a date:
1 2 3 4 5 6 |
struct DateStruct { int year{}; int month{}; int day{}; }; |
Enumerated types and data-only structs (structs that only contain variables) represent the traditional non-object-oriented programming world, as they can only hold data. In C++11, we can create and initialize this struct as follows:
1 |
DateStruct today { 2020, 10, 14 }; // use uniform initialization |
Now, if we want to print the date to the screen (something we probably want to do a lot), it makes sense to write a function to do this. Here’s a full program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> struct DateStruct { int year{}; int month{}; int day{}; }; void print(const DateStruct &date) { std::cout << date.year << '/' << date.month << '/' << date.day; } int main() { DateStruct today { 2020, 10, 14 }; // use uniform initialization today.day = 16; // use member selection operator to select a member of the struct print(today); return 0; } |
This program prints:
2020/10/16
Classes
In the world of object-oriented programming, we often want our types to not only hold data, but provide functions that work with the data as well. In C++, this is typically done via the class keyword. The class keyword defines a new user-defined type called a class.
In C++, classes and structs are essentially the same. In fact, the following struct and class are effectively identical:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct DateStruct { int year{}; int month{}; int day{}; }; class DateClass { public: int m_year{}; int m_month{}; int m_day{}; }; |
Note that the only significant difference is the public: keyword in the class. We will discuss the function of this keyword in the next lesson.
Just like a struct declaration, a class declaration does not allocate any memory. It only defines what the class looks like.
Warning
Just like with structs, one of the easiest mistakes to make in C++ is to forget the semicolon at the end of a class declaration. This will cause a compiler error on the next line of code. Modern compilers like Visual Studio 2010 will give you an indication that you may have forgotten a semicolon, but older or less sophisticated compilers may not, which can make the actual error hard to find.
Class (and struct) definitions are like a blueprint -- they describe what the resulting object will look like, but they do not actually create the object. To actually create an object of the class, a variable of that class type must be defined:
1 |
DateClass today { 2020, 10, 14 }; // declare a variable of class DateClass |
Member Functions
In addition to holding data, classes (and structs) can also contain functions! Functions defined inside of a class are called member functions (or sometimes methods). Member functions can be defined inside or outside of the class definition. We’ll define them inside the class for now (for simplicity), and show how to define them outside the class later.
Here is our Date class with a member function to print the date:
1 2 3 4 5 6 7 8 9 10 11 12 |
class DateClass { public: int m_year{}; int m_month{}; int m_day{}; void print() // defines a member function named print() { std::cout << m_year << '/' << m_month << '/' << m_day; } }; |
Just like members of a struct, members (variables and functions) of a class are accessed using the member selector operator (.):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> class DateClass { public: int m_year{}; int m_month{}; int m_day{}; void print() { std::cout << m_year << '/' << m_month << '/' << m_day; } }; int main() { DateClass today { 2020, 10, 14 }; today.m_day = 16; // use member selection operator to select a member variable of the class today.print(); // use member selection operator to call a member function of the class return 0; } |
This prints:
2020/10/16
Note how similar this program is to the struct version we wrote above.
However, there are a few differences. In the DateStruct version of print() from the example above, we needed to pass the struct itself to the print() function as the first parameter. Otherwise, print() wouldn’t know what DateStruct we wanted to use. We then had to reference this parameter inside the function explicitly.
Member functions work slightly differently: All member function calls must be associated with an object of the class. When we call “today.print()”, we’re telling the compiler to call the print() member function, associated with the today object.
Now let’s take a look at the definition of the print member function again:
1 2 3 4 |
void print() // defines a member function named print() { std::cout << m_year << '/' << m_month << '/' << m_day; } |
What do m_year, m_month, and m_day actually refer to? They refer to the associated object (as determined by the caller).
So when we call “today.print()”, the compiler interprets m_day
as today.m_day
, m_month
as today.m_month
, and m_year
as today.m_year
. If we called “tomorrow.print()”, m_day
would refer to tomorrow.m_day
instead.
In this way, the associated object is essentially implicitly passed to the member function. For this reason, it is often called the implicit object.
We’ll talk more about how the implicit object passing works in detail in a later lesson in this chapter.
The key point is that with non-member functions, we have to pass data to the function to work with. With member functions, we can assume we always have an implicit object of the class to work with!
Using the “m_” prefix for member variables helps distinguish member variables from function parameters or local variables inside member functions. This is useful for several reasons. First, when we see an assignment to a variable with the “m_” prefix, we know that we are changing the state of the class instance. Second, unlike function parameters or local variables, which are declared within the function, member variables are declared in the class definition. Consequently, if we want to know how a variable with the “m_” prefix is declared, we know that we should look in the class definition instead of within the function.
By convention, class names should begin with an upper-case letter.
Rule
Name your classes starting with a capital letter.
Here’s another example of a class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream> #include <string> class Employee { public: std::string m_name{}; int m_id{}; double m_wage{}; // Print employee information to the screen void print() { std::cout << "Name: " << m_name << " Id: " << m_id << " Wage: $" << m_wage << '\n'; } }; int main() { // Declare two employees Employee alex { "Alex", 1, 25.00 }; Employee joe { "Joe", 2, 22.25 }; // Print out the employee information alex.print(); joe.print(); return 0; } |
This produces the output:
Name: Alex Id: 1 Wage: $25 Name: Joe Id: 2 Wage: $22.25
With normal non-member functions, a function can’t call a function that’s defined “below” it (without a forward declaration):
1 2 3 4 5 6 7 8 |
void x() { // You can't call y() from here unless the compiler has already seen a forward declaration for y() } void y() { } |
With member functions, this limitation doesn’t apply:
1 2 3 4 5 6 |
class foo { public: void x() { y(); } // okay to call y() here, even though y() isn't defined until later in this class void y() { }; }; |
Member types
In addition to member variables and member functions, class
es can have member types or nested types (including type aliases). In the following example, we’re creating a calculator where we can swiftly change the type of number it’s using if we ever need to.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <iostream> #include <vector> class Calculator { public: using number_t = int; // this is a nested type alias std::vector<number_t> m_resultHistory{}; number_t add(number_t a, number_t b) { auto result{ a + b }; m_resultHistory.push_back(result); return result; } }; int main() { Calculator calculator{}; std::cout << calculator.add(3, 4) << '\n'; // 7 std::cout << calculator.add(99, 24) << '\n'; // 123 for (Calculator::number_t result : calculator.m_resultHistory) { std::cout << result << '\n'; } return 0; } |
Output
7 123 7 123
In such a context, the class name effectively acts like a namespace for the nested type. From inside the class, we only need reference number_t
. From outside the class, we can access the type via Calculator::number_t
.
When we decide that an int
no longer fulfills our needs and we want to use a double
, we only need to update the type alias, rather than having to replace every occurrence of int
with double
.
Type alias members make code easier to maintain and can reduce typing. Template classes, which we’ll cover later, often make use of type alias members. You’ve already seen this as std::vector::size_type
, where size_type
is an alias for an unsigned integer.
Nested types cannot be forward declared. Generally, nested types should only be used when the nested type is used exclusively within that class. Note that since classes are types, it’s possible to nest classes inside other classes -- this is uncommon and is typically only done by advanced programmers.
A note about structs in C++
In C, structs can only hold data, and do not have associated member functions. In C++, after designing classes (using the class keyword), Bjarne Stroustrup spent some amount of time considering whether structs (which were inherited from C) should be granted the ability to have member functions. Upon consideration, he determined that they should, in part to have a unified ruleset for both. So although we wrote the above programs using the class keyword, we could have used the struct keyword instead.
Many developers (including myself) feel this was the incorrect decision to be made, as it can lead to dangerous assumptions. For example, it’s fair to assume a class will clean up after itself (e.g. a class that allocates memory will deallocate it before being destroyed), but it’s not safe to assume a struct will. Consequently, we recommend using the struct keyword for data-only structures, and the class keyword for defining objects that require both data and functions to be bundled together.
Rule
Use the struct keyword for data-only structures. Use the class keyword for objects that have both data and functions.
You have already been using classes without knowing it
It turns out that the C++ standard library is full of classes that have been created for your benefit. std::string, std::vector, and std::array are all class types! So when you create an object of any of these types, you’re instantiating a class object. And when you call a function using these objects, you’re calling a member function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <string> #include <array> #include <vector> #include <iostream> int main() { std::string s { "Hello, world!" }; // instantiate a string class object std::array<int, 3> a { 1, 2, 3 }; // instantiate an array class object std::vector<double> v { 1.1, 2.2, 3.3 }; // instantiate a vector class object std::cout << "length: " << s.length() << '\n'; // call a member function return 0; } |
Conclusion
The class keyword lets us create a custom type in C++ that can contain both member variables and member functions. Classes form the basis for Object-oriented programming, and we’ll spend the rest of this chapter and many of the future chapters exploring all they have to offer!
Quiz time
Question #1
a) Create a class called IntPair that holds two integers. This class should have two member variables to hold the integers. You should also create two member functions: one named “set” that will let you assign values to the integers, and one named “print” that will print the values of the variables.
The following main function should execute:
1 2 3 4 5 6 7 8 9 10 11 12 |
int main() { IntPair p1; p1.set(1, 1); // set p1 values to (1, 1) IntPair p2{ 2, 2 }; // initialize p2 values to (2, 2) p1.print(); p2.print(); return 0; } |
and produce the output:
Pair(1, 1) Pair(2, 2)
(h/t to reader Pashka2107 for this quiz idea)
b) Why should we use a class for IntPair instead of a struct?
![]() |
![]() |
![]() |
Awesome lesson! I'm starting to make the connection to Unreal Engine's Visual Blueprints and what that emulates here in backend code!
Declare a class Learner as an ADT. The class Learner has two string member variables name and teacher. Provide the class declaration (interface) for the class Learner. The class Learner has the following member functions:
a default constructor
an accessor for the member variable name
a mutator for the member variable teacher
a bool member function checkTeacher()taking one parameter representing a teacher’s name to determine whether a learner is in a specific teacher’s class
an overloaded == operator for the class Learner so that it returns true if the teachers’ names of the two arguments are the same
an overloaded stream extraction operator for the class Learner.
Could you please give an example of why you say:
"For example, it’s fair to assume a class will clean up after itself (e.g. a class that allocates memory will deallocate it before being destroyed), but it’s not safe to assume a struct will. "
See my comment here
https://www.learncpp.com/cpp-tutorial/82-classes-and-class-members/comment-page-4/#comment-475083
A question regarding the following statement in the lesson:
"it’s fair to assume a class will clean up after itself (e.g. a class that allocates memory will deallocate it before being destroyed), but it’s not safe to assume a struct will."
Why is this? Structs can have constructors and destructors just as classes, so why would it not be safe to assume they deallocate memory when being destroyed just as classes? Programming conventions? Or is there some internal differences between structs and classes beyond the default access modifiers that are in play here?
Thanks!
Structs could very well clean up after themselves, but by convention, they don't. I suppose that's because structs originally came from C, which doesn't have any automatic cleanup.
I think we don't need curly braces at `m_day{}` on line 12.
Whoopsie, I guess I was search and replacing :D
There some basic errors to learn about more i recommend you to search more here answers can be solved but it ma take long it so i think you have to search more
Green box alert in the note about structs: "Rule: Use the struct keyword for data-only structures. Use the class keyword for objects that have both data and functions."
>>Note that since classes are types, it’s possible to nest classes inside other classes
Do you mean the following code by statement above?
It means this
>>And when you call invoke a function using these objects, you’re calling a member function.
isn't 'call' or 'invoke' extra?
>>he determined that they should, in part to have a unified ruleset for both.
isn't 'to' incorrect here?
>>You’ve already seen this as std::vector::size_type, where size_type is an alias for an unsigned integer.
>>Template classes, which we’ll cover later, often make use of type alias members.
I have always wanted to know those. I just found that! The beauty of C++ language is you know what is happening behind the scenes. I LOVE LOVE this language and I LOVE Alex and Nascadriver <3<3<3<3<3<3 Thank you thank you. This knowing and learning wouldn't be that interesting without you guys.
>>Using the class keyword defines a new user-defined type called a class.
isn't 'using' extra?
I think it makes more sense to have the
class use
instead of
since it only has a print function
Also, should we be zero initializing classes like we do with structs?
instead of
`std::string_view` is non-owning. If the employee doesn't own their name, someone else has to own it. `std::string` owns the string.
I updated the lesson to initialize the members
using number_t = int;
for (Calculator::number_t result : calculator.m_resultHistory);
What do you mean by those two sentences?
That's a type alias and a range-based for-loop. See lesson 6.13 and P.6.12a
You need to remove the line feed left behind by `std::cin >> n` before you can use `std::getline`.
See section "Mixing std::cin and std::getline()" in lesson S.4.4b.
"For example, it’s fair to assume a class will clean up after itself (e.g. a class that allocates memory will deallocate it before being destroyed), but it’s not safe to assume a struct will."
In the next section (8.3) you explain that structs and classes are essentially the same. I felt a bit uncertain about this passage quoted. Maybe it would be more clear to say "class will be written to":
"For example, it’s fair to assume a class will be written to clean up after itself (e.g. a class that allocates memory will deallocate it before being destroyed), but it’s not safe to assume a struct will."
Your recommendation to differentiate between structs and classes is mostly a convention as I understand it.
Quiz 1:
I couldn't understand why the code in "Member types" example:
created double output as:
7
123
7
123
The first
7
123
is from these lines
The loop prints another
7
123
When we call “y.print()” at line 16, Does the compiler interpret m_year as y.m_year or x.m_year at line 12?
Thanks
Every member function has a hidden parameter that is the object it's called on. We explain the this-pointer later in this chapter.
So after learning about classes, I decided to try my hand at building a maze generator.
Cool idea, right? My issue (which was my problem originally with C) was that the following function will not compile correctly:
I know why, I know where, but I don't know how to fix it. I can't return -1, I can't return false because it's not a reference... am I totally stuck?
One solution I thought of is having a dummy cell... but that is just clunky. I don't want to remove my error checking (god knows I am horrible at implementing algorithms the first time through), but I don't want to sprinkle bound checking into every algorithm loop I write to generate the maze with, either.
So... ideas as to how to fix my issue would be appreciated
Change the return type to `Cell*` and return a `nullptr`. Pointers are the same as references, but can be `nullptr`.
You say that global variables are evil, but member variables are kind of global inside class. I mean functions with full access to variables and they are not protected in any way. Doesn't this contradict?
No. Global variables are evil because any function in the entire program can change their values. Member variables can (typically) only be accessed by the members of the class, and even if access functions are provided, those access functions are only accessible by functions that have access to the object.
ok, thanks... on repl.it a .cquery file can be added to configure the compiler...
I'm trying to do the quiz, but in Repl.it , the solution gets compiler error at line
IntPair p2{ 2, 2 };
[cquery] expected ';' at end of declaration
You need C++11 or newer to use brace initialization. On a quick glance, I can't find a way of changing compiler settings on repl.it.
If you don't need user input, you can use godbolt https://godbolt.org/z/vn4_9h
We know that a pentagon is made up of five different points, so we can say that a pentagon “has” five points. Now what is a point? A point is something that has two dimensions x and y. After we are clear about polygons and points let’s try to answer following questions.
1- If you reach here, then you may have already spent a decent amount of time doing composition. What do you think how much time it will take to perform following tasks?
a. How much classes should be there?
b. Write definition of them.
c. How many data members should be in every class and why?
d. Write a function to calculate area of polygon. Search google for area function of polygon.
e. Write a function to calculate distance between two points of a polygon. You can use distance formula to perform this task.