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

Classes can (and almost always do) 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

1a) What is a public member?

Show Solution

1b) What is a private member?

Show Solution

1c) What is an access specifier?

Show Solution

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

Show Solution

2a) 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

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

Show Solution

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 length of the stack.
* A public member function named reset() that sets the length 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

232 comments to 8.3 — Public vs private access specifiers

  • Parsa

    I don't fully understand what you mean by per class and per object basis. Can you elaborate?

    And shouldn't the first output line for the last quiz be (0 0 0 0 0 0 0 0 0 0)

    • Alex

      People often assume that a member function only has access to the private data of the object the member function is operating on (the object pointed to by *this) -- but this isn't true. Rather, a member function has access to the private data of any object of that type, even if that object is passed in as a parameter (which makes sense, since *this is actually a hidden function parameter anyway). That's all I am getting at.

      As for the output, no, because the loop in print() goes from 0 to m_next, and m_next is initially set to 0 -- so the loop doesn't print anything.

  • Jon

    Hi guys! I'm having a strange issue on Xcode and was wondering if you might be able to help. When I copy and paste your solution code from 2a, I get the following output:

    Point(4.24399e-314 , 1.4822e-323 , 6.95313e-310)

    Copying, pasting, and compiling the same exact code in Visual Studio gives the expected output so it must be some setting somewhere in Xcode? Any idea what the compiler is actually doing here?

    • Jon

      Interestingly, if I make the member variables public and replace the print() call with the following,  it works fine in XCode:

      std::cout << "<" << point.m_x << ", " << point.m_y << ", " << point.m_z << ">";

      Something weird is happening with that member function call?

    • 2a prints angle brackets, integers and no "Point". Your output doesn't look like output of quiz 2a, make sure you saved the file before compiling it.

      • Jon

        Just figured it out! noticed that I still had an old "Point3d.h" file in my project folder leftover from when I originally went through these tutorials last year!

        I didn't #include it in Main.cpp but it was still messing things up somehow. As soon as I deleted it I got the expected output. Still not sure EXACTLY what's going on but the issue is fixed.

  • Nguyen

    Hi,

    In the last example, when we call copyFrom(), d becomes a reference to main’s date variable.
    Does this mean d.m_month refers to date.m_month in main(), d.m_day refers to date.m_day in main() and d.m_year refers to day.m_year in main()?  If yes, why day in main() can have access to date’s private members?

    Thanks....

    • Yes. `copyFrom` is accessing private members of `DateClass`. `copyFrom` is itself a member of `DateClass`, so it can access private members. It doesn't matter where `copyFrom` was called from.

      • Nguyen

        I have no problem with copyFrom().  I just don't understand why d.m_month refers to date.m_month when copyFrom() is called.  We know that date.m_month is not allowed in main().  How could date.m_month be referred when it can't be created in main()?

        • > I just don't understand why d.m_month refers to date.m_month when copyFrom() is called.
          `date` is passed to `copyFrom` in line 38. This is unrelated to classes and access modifiers, re-read lesson 7.2 - 7.4.

          > How could date.m_month be referred when it can't be created in main()?
          `date.m_date` created when `date` is created in line 24. `main` can't access `date.m_month`, but `date` is passed to something that has access to `DateClass`' members.

  • Anastasia

    Hi!
    Quiz 3.
    Sorry if this question was already asked and answered, but why is it necessary to make array length (in my case `int max_elements { 10 }` (line 5)) const AND static? My compiler didn't want to accept non-static const/constexpr (error: invalid use of non-static data member ‘Stack::max_elements’) and static non-const declarations (error: ISO C++ forbids in-class initialization of non-const static member ‘Stack::max_elements’).

    edit:fixed magic value in line 16

    • Anastasia

      It seems that static members CAN be declared inside a class, but initialization inside the class is not allowed(?) unless they are const (in this case they can be initializied either inside or outside of the class) or constexpr (in this case they must be initialized inside the class). Is that correct? And if it is, does it mean that (non-const) static members should be always public (so that they can be accessible outside of the class)?
      And same question about (non-static) const member variables, which also can be declared, but not defined inside a class.

      • Alex

        While all of your exploration is great, you might want to read lesson 8.11 and 8.5b, which cover static member and non-static member initialization respectively.

        • Anastasia

          I should have read those before asking. Noticed too late that this is going to be covered eventually, my apologies. Thank you for this wonderful resource, I can't even imagine wrapping my head around all these complicated concepts without your lessons and nascardriver's explanations and corrections.

    • > why is it necessary to make array length [...] const AND static?

      `max_elements` can be modified during construction, but line 6 requires a compile-time constant. Declaring it `static` prevents all modifications.

      `max_element` is the same for all instances of `Stack`. There's no point in having a copy of it in every instance. It should be static.

      > static non-const declarations
      `static` non-const is the same as a global variable. You can't define it in a header, because there will be multiple definitions (Once in each file that includes the header).
      Declaring it `static const` or `static constexpr` tells the compiler that this variable always has the same value. As such, the compiler doesn't have to worry about multiple definitions, as they'll all be the same.

      > Is that [inside/outside definition] correct?
      Mostly. Non-const static members have to be define outside to class so that there is only 1 definition of them. The exception are `inline` variables.

      `inline` variables will only be defined once. To achieve this, the compiler has to generate code that checks, at run-time (!), if the variable has already been initialized. If you define the variable outside the class, no such code is required.
      If you want to use a `static` member as a compile-time constant, you have to define it at its declaration. Back to your code, if `max_elements` was `static const` or `static constexpr` and defined outside the class, line 6 wouldn't know its value.

      > (non-const) static members should be always public (so that they can be accessible outside of the class)?
      No. Just like private functions, private static members can be defined outside of the class.
      Normal visibility rules apply. If you don't need it to be public, don't declare it public.

      • Anastasia

        Thanks a lot for such a comprehensive response! I should also come back to it and re-read it one more time later when I learn more about classes.
        Just one little clarification, please, since I'm already wasting your time:

        > if `max_elements` was `static const` or `static constexpr` and defined outside the class, line 6 wouldn't know its value.
        I thought static constexpr must be defined and initialized inside the class, is it not the case?

  • Xueyang

    In the solution of the last quiz, I don't understand how pop() actually pops out an element of the array. In my code, I set the last element of the array to be zero and it seems to work. Could you please illustrate more on this? Thanks.

    • - Initialize your variables with brace initializers.
      - Use single quotation marks for characters.
      - Use ++prefix unless you need postfix++. Same for --.
      - Don't use `using namespace`, it can lead to name conflicts.
      - Magic number: 10.

      You're setting the popped element to 0, but how are you going to access that element now that it's popped? You can't, so there's no need to set it to 0. It only gets available once something has been pushed into its place, doing so overrides to old value.

      • Xueyang Wu

        Thank you very much for pointing out the problems in the code. Regarding the pop() function, I used a temp variable to store the variable that is going to be popped. Because temp is a copy, not a reference, popping element will not change temp. Then from what I understand, popping an element off the stack meaning removing this element, that's why I set it to zero. However, in the solution, what pop() does is exit if the stack is empty, otherwise return the popped element. I am wondering how it is actually removing the popped element. Thanks for helping.

  • mmp52

    Hey!
    Again a simple question related to the scope problem that I have,

    Why do I have to put "static" before defining the

    ? When I do it without static, compiler gives the error:
    "a nonstatic member reference must be relative to a specific object".
    So I have to correct the variables as follows:

    • `m_fixed_array_length` can be modified by a constructor (Covered later). `m_fixed_array`'s length would be unknown at compile-time. This doesn't work.

      By declaring the 2 variables static, there's no sane way of modifying `m_fixed_array_length`, thus the size of `m_fixed_array` is known.

      If `m_fixed_array` is static, it's not bound to an instance and you can't have more than 1 stack.

      • mmp52

        Hello,
        Thank you for your answer.
        But when I define it as a "const int" (without using static), doesn't it already have a fixed length known at compile time since it is a "const"?

  • Problem 3 line 3 :
    "If you need a refresher on a what a stack is."
    Should be :
    "If you need a refresher on what a stack is."

  • Nguyen

    Hello Alex & nascardriver,

    If all the member variables belong to private, it will complain at line 26.
    If all the member variables belong to public, it works as expected.

    Could you please explain?

    Thank you,

    • Private members can only be accessed from other members.
      @copyFrom is not a member of @DateClass, so it's no allowed to access private members.

      • Nguyen

        copyFrom() is NOT a member of DateClass?
        I think it is a member of the class itself (line 20).

        • Please use your editor's auto-formatting feature, your indention is misleading.
          @DateClass doesn't have a constructor that accepts 3 arguments. Until constructors have been covered (Lesson 8.5), you need to declare the class members public to allow aggregate initialization.

  • ntdong

    - To solve the last solution and to avoid assignment for the fixed array, I use vector in the program.

    - In previous chapters, I have read. The coders should avoid to assign the length for the fixed array. I see in the solution, this makes me confuse. Particularly:

    • ntdong

      - Thanks. I have read again. It is suitable for symbolic constant. Example:

      [coder]
      // using a symbolic constant
      const int arrayLength = 5;
      int array[arrayLength]; // Ok
      [/coder]
      - Avoid for non-const variable or runtimeconstant, like that:

      [coder]
      // using a non-const variable
      int length;
      std::cin >> length;
      int array[length]; // Not ok -- length is not a compile-time constant!
      // using a runtime const variable
      int temp = 5;
      const int length = temp; // the value of length isn't known until runtime, so this is a runtime constant, not a compile-time constant!
      int array[length]; // Not ok
      [/coder]

  • Beto

    Hi, in the excercise 3, the functions push and pop could they be void? I don't see why we need a return value.

  • Max Morgan

    Hey Alex/Nascar,

    In quiz 2b:

    I understand why we use p.m_x, p.m_y, and p.m_z to access the objects of class instance p, which is the argument of isEqual. We want to see if these are the same as the objects of another (unspecified in this function) class instance. I tried to use point1.m_x instead of just m_x because I was not sure of how isEqual would know which class instance we wanted to compare its argment &p against. I received an error that said point1 was not known within this scope. I now understand that isEqual looks at the objects of the class instance that calls it and compares them to &p. I was confused on this and wanted to make a comment in case I have misunderstood anything (please share your comments) or this helps someone who is confused on this as I was.

  • Louis Cloete

    Hi!

    In Quiz Question #3:

    A range-based for loop can be used in the reset() function. Using a range-based for loop is IMHO better style in any case where it is possible to use, since it will prevent off-by-one errors. The  set to 0 of all array elements is also unnecessary, since it will be overwritten as the stack grow anyway.

  • Saj

    Hi Alex!
    In chapter relational operators, you taught us that double shouldn't be compared with == operators. But in point3d.isEqual() you compared x y z's with ==
    Maybe you should use int for m_x, y, z or use approximatelyEqual() function to compare them

  • Jon

    Hello, I have a couple questions about Quiz item #3 using std::array -

    At first, my compiler (Xcode) gave me a few warnings about the implicit conversion between unsigned and signed integers resulting from comparisons between type size_t and int, and having the array [] operator hold int types.

    I got the errors to go away by declaring my stackLength variable at the top as a size_t rather than an int.
    Is this a strange thing to do/bad practice? Should I instead declare stackLength as an integer and static cast all size_t values to integers throughout my code?

    Lesson 6.15 states "A better solution is to avoid manual indexing of std::array in the first place. Instead, use range-based for loops (or iterators) if possible." In this case, it doesn't seem possible to avoid manual indexing though, right? I know we're supposed to use std::array over built-in fixed arrays, but having to worry about signed/unsigned conversion issues seems to make things overly complex in this case. I guess std::array is just overkill for this particular exercise?

    How would just letting the compiler do the implicit conversion come back to haunt me later on if this program were more complex? If I don't fix the warnings, the results of this program come out as expected but it just doesn't feel "right"! Anyhow, here's my "fixed" code, with my stackLength variable at the top as a size_t.

    • Jon

      After taking another look at 6.15 it seems it would be better to use std::array<int, 10>::size_type for my stackLength variable (plus maybe a type alias)?
      Unless keeping it an int and static_casting on lines 20, 28, and 34 would be better practice...

    • Hi Jon!

      > Should I instead declare stackLength as an integer
      Nope, std::array<int, 10>::size_type is fine. Unsigned indexes are only problematic when you're using reverse for-loops (Going high to low). If you're using reverse for-loops or other fancy arithmetic on indexes, use an int and static_cast.

      > plus maybe a type alias
      Yep

      > How would just letting the compiler do the implicit conversion come back to haunt me later on
      You shouldn't get used to warning messages, because you'll start ignoring them.
      You won't be able to access high-index elements, but I doubt you'll have more than max_int/2 elements anyway.
      Other than that, I can't think of a problem that you wouldn't also have with signed ints and casts or @size_type.

  • nullptr

    What do you guys think about this implementation?

    class Stack
    {
    public:
        void reset()
        {
            for (auto &x : arr)
            {
                x = 0;
            }
            length = 0;
        }

        bool push(int value)
        {
            if (length < arr.size())
            {
                arr.at(length) = value;
                length++;
                return true;
            }

            return false;
        }

        void pop()
        {
            assert(length > 0);
            arr.at(length - 1) = 0;
            length--;
        }

        void print()
        {
            std::cout << " ( ";
            for (size_t i = 0; i < length; i++)
            {
                std::cout << arr[i] << " ";
            }
            std::cout << " )" << std::endl;
        }

    private:
        std::array<int, 10> arr{};
        int length = 0;

    };

    Edit: not sure why the text is not treated as code

    • Hi!

      > not sure why the text is not treated as code
      You didn't use code tags. See the yellow box below the reply text area.

      * Line 6-9 and 28: Unnecessary. These elements won't be accessible unless they're overridden. This loop could have been replaced with @std::fill or @std::array::fill
      * Line 15, 35: Comparison of signed/unsigned. Read compiler warnings and fix them. If you're not getting warnings, make sure you followed lesson 0.10 and 0.11.
      * Line 17, 18: Merge these lines or use ++prefix
      * Line 28, 29: Merge these lines and use --prefix
      * Line 35: Use @std::size_t
      * Line 35, 44: Initialize your variables with uniform initialization
      * Line 35: Use ++prefix unless you need postfix++

  • Alex A

    Hi All,

    my solution using std::array as opposed to a fixed array also used a for-each loop for convenience on reset. (i guessed you could make this even simpler by using std::vector's stack like behavior but this would feel to be avoiding the quiz!) please let me know any pointers on the below.

    • Hi Alex!

      * Line 10, 53: Initialize your variables with uniform initialization.
      * Line 8: Use constexpr for compile-time constants
      * Line 22: Should be >=
      * Line 16 and 17 are not necessary, because the elements won't be accessible anyway.
      * Line 40 should loop up to @m_next.

  • Codename 47

    Hi!
    A small typo (i think) -- "the public interface defines how programs using the class will interface with the class." The second word interface should be "interact", i guess.

  • magaji::hussaini

  • Rahul Kumar

    I think Alex forgot to set the last element to 0 in the pop method:
        
           int pop()

        {
            assert (m_next > 0 && "Stack is Empty");
            m_array[--m_next] = 0;
            return m_array[m_next];
        }

    • Hi Rahul!

      There's no need to do so. The element that was popped can no longer be accessed. The next time @push is called, the element will be overridden. Replacing the element with 0 is only useful for debugging purposes. You could enable it only in debugging mode

  • hnsdgvefeqg

    my solution

    • * Initialize your variables with uniform initialization
      * Inconsistent formatting. Use the auto-formatting feature of your editor
      * Line 27: Return a value that is recognizable as an error code (eg. -1). Even better, add an assert.
      * Arrays start at index 0, revisit the beginning of chapter 6
      * You're using the same naming convention for variables/types/functions. This will lead to confusion.

  • hnsdgvefeqg

    int pop()
        {
            // If there are no elements on the stack, assert out
            assert (m_next > 0 && "Can not pop empty stack");

                    // m_next points to the next free element, so the last valid element is m_next -1.
                    // what we want to do is something like this:
                    // int val = m_array[m_next-1]; // get the last valid element
                    // --m_next; // m_next is now one less since we just removed the top element
                    // return val; // return the element
                    // that can be condensed down into this:
            return m_array[--m_next];
        }

    how does the line
            return m_array[--m_next];
    alter the length of the array?

  • Gizmo

    For my pop() function, instead of using an int function and returning a value, I used a void function and didn't return anything.

    Here is the example code:

    And here is my code:

    Note: The function takes place inside a class.

    1.)Why would you want to return an element that will not be used outside of the class functions?
    2.)Which method would be preferred here?

  • Jeff

    Style-wise, for clarity at the minimum, I personally prefer always having an explicit "private:" access qualifier even if the language (be it C++ or other) defaults to private.

  • Ngô Hoàng Đạt

    For quiz 2:

  • Kio

    For 3. my solution, sorry was using "using namespace std",

    • Hi Kio!

      * Initialize your variables with uniform initialization
      * Magic number: 10
      * Line 20: Indention
      * Line 24: @operator! is applied to @lengthOfTheArray, returning a bool, which you're then comparing to 0. You were probably looking for !=.
      * Line 27, 28: Can be merged into 1 line
      * Line 63: Unnecessary, as there is no input in your program.

  • btnal

    Hi,
    Can you answer me some questions ?
    1. Private access is only used for attributes, and public access is only used for methods, true or false. explain ?
    2. Make an example for a class that has the access method( function) is private.
    3. In general, why should not public access properties be public? Make an example.

    Thank a lot

    • Hi!

      1. false. There are lots of reasons to have private functions.
      2. Not sure what you're looking for

      3. Because you don't want the coder using the class to write arbitrary values to a member variable. By using a function you can filter out invalid values.

  • Hi

    Having some issues with the last one.  My code is thus:

    but it prints columns of 0's, not as would be expected.  Is my assert check wrong because it should throw a zero out?

    • Hi Nigel!

      @reset should reset @m_Index
      The assert in line 33 seems wrong, but the assert doesn't fire so I'll leave this up to you.
      @print is printing a line feed after every element.

    • John

      Hi

      In this code you are not actually resetting or removing the value in the stack(unless you print the values till m_index), so even if you print the stack it will show the value you pop(). Irrespective of how many times you do the pop().

  • and 2(b):

    I added the calls to print() in main() so that we could see what we were comparing.

  • 2(a):

  • Silviu

    Hello,
    How does m_next keeps track of all the counting, if it's not static, like s_maxStackLength ? (I mean it passes trough all the member functions like a static variable). It's like s_maxStackLength=m_next ? or ?

    • Alex

      The lifetime of a normal member variable is tied to the lifetime of the object that contains that it. Put another way, m_next will exist as long as the class object that contains it exists.

      At the top of main(), we define Stack stack. This contains a member m_next. stack.m_next will exist as long as stack does. And stack exists for pretty much the whole program -- thus so does m_next.

      • Silviu

        ok ok, so why the program "forces me" to create s_maxStackLength static.
        s_maxStackLength is const so it doesn't changes at all.

        I know in lesson 8.2, your giving an example similar with how it's m_next but i never thought it could remember the values.
        And yes you explain shortly "the implicit object" i hope i'm not wrong.

        • Alex

          > const int s_maxStackLength{ 10 }; // using without static

          This creates a s_maxStackLength member for each Stack object created. While that will still work, it means you are wasting 4 bytes per object holding a constant that doesn't change per object.

          Instead, by making s_maxStackLength a static, only one s_maxStackLength gets created regardless of how many Stack objects we create, so we only pay the 4 bytes to hold the constant once per program, not once per Stack object created.

          • Silviu

            Thank you, it's saves memory and the static word has more meanings:
            - like holding a value trough the life of the program
            - doesn't creates duplicates every time.(so it works like a reference or a global variable)
            (i hope i'm not wrong)
            Probably i should expect more of this in c++...
            The member variable m_next doesn't waste memory ? Why we don't use a reference.

            • Alex

              Yes, a static member variable works like a global variable that is namespaced to the class itself.

              m_next doesn't "waste" memory because each Stack independently needs to track its length -- m_next does this.

              Why don't we use a reference for what?

              • Silviu

                Thank you for all the answers. I was meaning reference too m_next, but since i don't know how classes work yet, i'm a little confused.I will take my time with these, but thank you.

Leave a Comment

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