Search

8.3 — Public vs private access specifiers

Public and private members

Consider the following struct:

In this program, we declare a DateStruct and then we directly access its members in order to initialize them. This works because all members of a struct are public members by default. Public members are members of a struct or class that can be accessed from outside of the struct or class. In this case, function main() is outside of the struct, but it can directly access members month, day, and year, because they are public.

On the other hand, consider the following almost-identical class:

If you were to compile this program, you would receive errors. This is because by default, all members of a class are private. Private members are members of a class that can only be accessed by other members of the class. Because main() is not a member of DateClass, it does not have access to date’s private members.

Access specifiers

Although class members are private by default, we can make them public by using the public keyword:

Because DateClass’s members are now public, they can be accessed directly by main().

The public keyword, along with the following colon, is called an access specifier. Access specifiers determine who has access to the members that follow the specifier. Each of the members “acquires” the access level of the previous access specifier (or, if none is provided, the default access specifier).

C++ provides 3 different access specifier keywords: public, private, and protected. Public and private are used to make the members that follow them public members or private members respectively. The third access specifier, protected, works much like private does. We will discuss the difference between the private and protected access specifier when we cover inheritance.

Mixing access specifiers

A class can (and almost always does) use multiple access specifiers to set the access levels of each of its members. There is no limit to the number of access specifiers you can use in a class.

In general, member variables are usually made private, and member functions are usually made public. We’ll take a closer look at why in the next lesson.

Rule

Make member variables private, and member functions public, unless you have a good reason not to.

Let’s take a look at an example of a class that uses both private and public access:

This program prints:

10/14/2020

Note that although we can’t access date’s members variables m_month, m_day, and m_year directly from main (because they are private), we are able to access them indirectly through public member functions setDate() and print()!

The group of public members of a class are often referred to as a public interface. Because only public members can be accessed from outside of the class, the public interface defines how programs using the class will interact with the class. Note that main() is restricted to setting the date and printing the date. The class protects the member variables from being accessed or edited directly.

Some programmers prefer to list private members first, because the public members typically use the private ones, so it makes sense to define the private ones first. However, a good counterargument is that users of the class don’t care about the private members, so the public ones should come first. Either way is fine.

Access controls work on a per-class basis

Consider the following program:

One nuance of C++ that is often missed or misunderstood is that access control works on a per-class basis, not a per-object basis. This means that when a function has access to the private members of a class, it can access the private members of any object of that class type that it can see.

In the above example, copyFrom() is a member of DateClass, which gives it access to the private members of DateClass. This means copyFrom() can not only directly access the private members of the implicit object it is operating on (copy), it also means it has direct access to the private members of DateClass parameter d! If parameter d were some other type, this would not be the case.

This can be particularly useful when we need to copy members from one object of a class to another object of the same class. We’ll also see this topic show up again when we talk about overloading operator<< to print members of a class in the next chapter.

Structs vs classes revisited

Now that we’ve talked about access specifiers, we can talk about the actual differences between a class and a struct in C++. A class defaults its members to private. A struct defaults its members to public.

That’s it!

(Okay, to be pedantic, there’s one more minor difference -- structs inherit from other classes publicly and classes inherit privately. We’ll cover what this means in a future chapter, but this particular point is practically irrelevant since you should never rely on the defaults anyway).

Quiz time

Question #1


a) What is a public member?

Show Solution

b) What is a private member?

Show Solution

c) What is an access specifier?

Show Solution

d) How many access specifiers are there, and what are they?

Show Solution

Question #2


a) Write a simple class named Point3d. The class should contain:
* Three private member variables of type int named m_x, m_y, and m_z;
* A public member function named setValues() that allows you to set values for m_x, m_y, and m_z.
* A public member function named print() that prints the Point in the following format: <m_x, m_y, m_z>

Make sure the following program executes correctly:

This should print:

<1, 2, 3>

Show Solution

b) Add a function named isEqual() to your Point3d class. The following code should run correctly:

Show Solution

Question #3


Now let’s try something a little more complex. Let’s write a class that implements a simple stack from scratch. Review lesson 7.9 -- The stack and the heap if you need a refresher on what a stack is.

The class should be named Stack, and should contain:
* A private fixed array of integers of length 10.
* A private integer to keep track of the size of the stack.
* A public member function named reset() that sets the size to 0.
* A public member function named push() that pushes a value on the stack. push() should return false if the array is already full, and true otherwise.
* A public member function named pop() that pops a value off the stack and returns it. If there are no values on the stack, the code should exit via an assert.
* A public member function named print() that prints all the values in the stack.

Make sure the following program executes correctly:

This should print:

( )
( 5 3 8 )
( 5 3 )
( )

Show Solution


8.4 -- Access functions and encapsulation
Index
8.2 -- Classes and class members

284 comments to 8.3 — Public vs private access specifiers

  • Fatih Parlak

    Hi,
    In the last question when you print stack, you clear and when the program returns the main,the program tries to clear stack again. This cause undefined behavior

    • nascardriver

      There are no dynamic allocations in the last quiz, there cannot be double frees.
      Clearing the stack by assigning it an empty stack does not free its memory, it only changes its values.

      • Fatih Parlak

        What I meant was this code. We pop elements until the stack is empty and when the program returns the main, it can not pop any element because there is no element left in stack so the program crash. Am I missing something? Because in my computer it does crash

        • nascardriver

          Nothing is popped when the program exits. We're only popping in `printStack`, and in there we only pop if the stack is not empty.
          The program should not crash. Please use a debugger to figure out where it's going wrong and share the results.

  • Karl Phazer

    For once I have (if surely not better but) alternative (yet feasible) implementation to a Quiz assignment.

    So here's my take on Q3. While std::array might give some benefits, the assignment can be done with a basic array. As there are no performance or memory constraints set, the real size of the actual array isn't essential, only the part that is given access to the user. There's also a bonus option to resize the stack up to 50 elements :)

    I had to comment out the assert line for the program to execute through to the end, is this how it is supposed to work?

    Thanks!

    • nascardriver

      Apart from the `assert`, everything is nice :)
      `assert` asserts that the condition is `true`. `pop` will only run if `m_size == 0` is `true`, ie. you can only pop empty stacks.

      • Karl Phazer

        Well, dang it!

        I have a bad habit of skipping or at least reading hastily through error checking etc. stuff and this is how it shows. Thanks!

  • Aryan

    A question is Container type alias really necessary since we are using it once if we want to change the type or size of the array we can also do it in the declaration Whoops nevermind i realised it is also needed in

    thanks :)

  • Tony

    Hi nascardriver, again! All good?

    First off, for quiz 3, could you explain what's the meaning of this:

    And why are you using that? Maybe I forgot something there... Thanks.

    For some reason in quiz 3 I thought that you meant an actual C-style string array, so that's what I used for my solution. It works, but perhaps I'm missing something there. Could you check it whenever you have some spare time and give me some suggestions? Thank you for the great tutorial once again. :)

    ***EDIT***
    Why can't we use void for pop()?

    • nascardriver

      Hello!

      Using `int` for indexing is bad, because you'll need casts all over the place. The correct index type for `std::array` is `std::array::size_type`. Typing that out is tedious and not easy to update, so we declare `size_type` as an alias for `std::array::size_type`.

      - Line 15: Magic number
      - Misleading formatting. Use an auto-formatter
      - Line 31: List initialization

      If `pop()` returns `void`, there's no way to access the elements of the stack. You either need a `top()` function that returns the element on top or have `pop()` return the popped element.

  • Imagine

    Why were the variables in the class declared like this in Solution of Question #2, is this not a bad practice?

  • Gustaw

    While compiling a solution to the third question on the CodeBlocks 20.03, I get an error in the line 5:"Stack::m_array should be initialized in the member initialization list [-Werror=effc++]".

  • [/code]#include <iostream>

    class Point3d{
      private:
        int m_x{};
        int m_y{};
        int m_z{};
      public:
        void setValue(int first, int second, int third){
          m_x = first;
          m_y = second;
          m_z = third;
        }

       void print(){
         std::cout << "< " << m_x << ", " << m_y << ", " << m_z << ">";
       }
    };[/code]

  • Apple

    In solution of question 2 point a function 'print()' you forgot about the '\n'
    So instead of

    it should've been

    I know it was only used once but the good practise should've been kept in my opinion.
    Thanks for the tutorials and have a good day! :D

    • nascardriver

      By including the line feed in the `print` function, we're preventing the user of the class from printing things like

      "My point is <1, 2, 3> and it's sunny outside"
      If `print` printed the line feed, we could only do
      "My point is <1, 2, 3>
      and it's sunny outside"

      I added

      the the places that call `print` instead. Thanks!

  • previous access specifier

    >>Each of the members “acquires” the access level of the previous access specifier (or, if none is provided, the default access specifier).

    what do you mean by previous access specifier here? Isn't it better to say "Each of the members “acquires” the access level of the closest access specifier defined"?

  • Luxary

    Hey guys, thanks once again for these awesome tutorials :)
    One observation: is there any specific reason to handle the stack indexing with @m_next in Quiz #3?

    While I do get that using size_type (unsigned int) is good practice for array indexing, I feel it would be much better to emphasize on the actual stack pointer status, since it's explained quite well in Lesson 7.9.

    In the current solution the pointer position is "hidden", and in the code comments you had to clarify that the last valid element is (m_next - 1), which is confusing and makes it hard to picture the status of the stack.

    This is how I wrote it (surely less fancy than the actual solution):

    Now, throughout the code, @m_pointer will clearly point at the current top value in the stack, rather than having a variable that indicates the next available slot. Wouldn't it be better to track the actual stack pointer position?

    Just my two cents!

  • Vuong

    Hi. First of all, thank you for your tutorial. It opens up a lot to me.
    I just couldn't think of the solution for the pop() function so I had to look up yours.
    From the looks of it, it seems that you just return the last valid element by moving the pointer backwards in the array. Then you can simply not print that last pointed element int the array? Am I right? Because I can't see that you "remove" the top most element. Please correct me.

  • rupp_bin

    Problem with stack question
    my code is not working;it was giving 2 at arr[0] can I know whats wrong with my code

  • SeanW

    Thanks for the great tutorials!
    I have a question about question#3. It's not clear to me why function push() returns a bool and function pop() returns an int. The functions do not need to return a value, they just need to modify m_next and m_array, which they can do without return values. I wrote the program with both push() and pop() as void return functions and it worked.

    • nascardriver

      If `push` doesn't return a `bool`, you can't know when the stack is full. If `pop` doesn't return an `int`, you can't access any values on the stack. A stack which can't be accessed is useless.

  • Rahul Kumar Sethi

    I was unable to understand why are we using container type and why not a normal array and not std::array.
    I got an error when i used array instead of std::array in the code.

    • nascardriver

      We could use `std::array` directly, but we'd have to repeat `std::array` everywhere. This takes time and is prone to human error, and it makes updating the code more difficult.
      If we ever decide to make the stack larger or change the element type, we only have to update the type alias and function declarations.

  • Nexteon

    Know there are flaws in my code. The biggest hurdle to me was understanding assert. I was trying to avoid ++ increment because of overhead issues. Otherwise, the quiz was easy. I could do better though.

    • nascardriver

      There's zero overhead from using --prefix and ++prefix. Only the postfix++ versions have an overhead.

      - Line 16: Magic number
      - Misleading formatting. Please use your editor's auto-formatter.
      - I don't understand what you're trying to achieve with line 18,19. Now your stack can't handle negative numbers.
      - `pop` should only pop if there is something on the stack, so the assertion should be `m_length > 0`. If you do that, `m_length` can't go negative and you don't need line 32,33.
      - `print` should use `m_length`. Avoid use magic values to indicate an error. `m_length` tells you how many valid elements you have.

      Your program exhibits undefined behavior, because `m_array` is used uninitialized. For me, your program produces
      ( 32661 4197776 4196480 749996032 32767 )
      ( 5 3 8 4197776 4196480 749996032 32767 )
      ( 5 3 4197776 4196480 749996032 32767 )
      ( 4197776 4196480 749996032 32767 )
      You can get away without initializing `m_array` if you use `m_length` rather than your magic 0 and negative numbers.

  • Ian

    Hi!
    I wonder why I can't use "std::vector<int> array(10)" to create a vector with default element which I can use in main() function.
    Is there any restriction to use it?
    Thanks!

  • Gabe

    Quiz 2 & 3:

    Quiz 3:

  • shinji

    Hello. I have a question. Where did "container_type::size_type" in the following bit of the quiz solution come from?

    • nascardriver

      `container_type` is `std::array`.
      `std::array` has a member type, `size_type`. We set our `size_type` as an alias to the array's `size_type`.

  • mansbota

    I used std::array, didn't see it had to be fixed.

    Stack.h

    Stack.cpp

    • nascardriver

      Using `std::array` here is totally fine. I'll consider updating the lesson to `std::array`.

      Congratulations on solving the quiz! some suggestions:
      - Initialize variables with brace initialization for higher type-safety (Stack.cpp:22).
      - Use --prefix unless you need postfix-- (Stack.cpp:4).
      - Magic number in Stack.cpp:10. Use `maxSize` or `m_stack.size()`.

  • Mn3m

    3)
    My result is identical to yours. However, I think there's something wrong with my pop function regarding the return value!
    Do I still need to return it or is it needless depending on my implementation of popping?

    • nascardriver

      It's up to you if `pop` returns the element or not. In your implementation, if you don't return the element, there's no way to access the stack elements. If you decide to not return the popped element, you should add another access function (eg. `top`).

  • Mn3m

    • nascardriver

      You never need an if-statement of this form

      You can just

  • Fabio Henrique

    I was having a lot of issues completing some exercises today, and now I think I realized why... I was trying always to use `std::array` instead of `array`, I guess on the exercises on this page the correct is to use an `array` instead of an `std::array` right ?!

    • nascardriver

      You can use an `std::array` wherever you can use a fixed-size C-style array. Is there any exercise is specific where this was an issue?

  • Charan

    Hey,
    In the last quiz problem, is it possible to give the value of s_maxStackLength during runtime?

  • Ged

    1. We are using (constexpr / const) because when declaring a fixed array it requires it?
    2. Why is not using a "static" generate a compile error?

    • nascardriver

      1. Every value that is known at compile-time should be `constexpr`. This reduces the load on the run-time.

      2. The value of `constexpr` variables can't change. Declaring it as a non-static member would create a copy of the variable in every instance of the class. That doesn't make sense, because all the values will be the same.

  • Wallace

    Typo of plural vs. singular agreement: "Classes can (and almost always do) use multiple access specifiers to set the access levels of each of its members." Suggested rewording: "A class can (and almost always does) use multiple access specifiers to set the access levels of each of its members."

  • ErwanDL

    Hey A and N,

    I think the instructions for the last exercise of the quizz are a bit confusing :

    "The class should be named Stack, and should contain:
    * A private fixed array of integers of length 10.
    * A private integer to keep track of the length of the stack.
    * A public member function named reset() that sets the length to 0."

    I think "length" is somewhat misused in there. Maybe it would be clearer to say "A private fixed array of integers of CAPACITY 10." for the first instruction.

    But maybe using "capacity" would also confuse people who may think this word is only relevant when speaking of std::vector, since it was introduced in lesson 7.10 about std::vector. In that case, you could keep the word "length" for the first instruction, but replace it in the 2nd and 3rd instructions with "cursor position", "stack pointer position" or something along those lines.

    What do you guys think ?

    • nascardriver

      I updated the wording of point 2 and 3 to use "size" rather than "length", as stacks grow upwards, not sideways.
      "length" in the first point is correct. The array always has the same length. The stack has a capacity, the array doesn't.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]