Classes and Objects

Struct Versus Class

In C++, the keywords struct and class are used to define types that are nearly identical. Both may contain data members, member functions, constructors, and access specifiers. The programmer may use both keywords to create objects of these types. The one difference between the two keywords from a language perspective is the default access that is applied to any members which are not accompanied by an access specifier, that are not in a body, and such things as default initialisation for members.

A struct's default access is public. A class's default access is private.

Everything else remains the same.

The two following definitions are written with the smallest code possible:

#include <iostream>

struct PointStruct
{
    int x{};
    int y{};
};

class PointClass
{
    int x{};
    int y{};
};

int main()
{
    PointStruct s{};
    s.x = 3;
    s.y = 4;
    std::cout << s.x << ',' << s.y << '\n';

    PointClass c{};
    // c.x = 3;   // compile error: x is private
    // c.y = 4;   // compile error: y is private

    return 0;
}

In PointStruct, members x and y are public by default. We may then access them directly from main. In PointClass, x and y are private by default.

They are inaccessible from main, so the compiler may not let us do so.

We have labeled them so: adding public: before x and y would make the class function like a struct. Adding private: before the members of the struct would make them act like a class. Nothing about the meaning of the keywords other than default access changes.

Though their language difference is minimal, programmers of C++ have come to use a stricter convention to distinguish struct and class than the compiler uses. A struct generally refers to a "pod" type, Plain Old Data, which only wraps public data and has no rules about their own invariant. A class usually refers to a type that has a rule or invariant about its state which needs to be maintained and thus has to hide the data members.

An example of a simple aggregate could be a struct defining RGB color component values:

#include <iostream>

// Define the members that belong to this type
struct Color
{
    int red{};
    int green{};
    int blue{};
};

int main()
{
    Color c{};
    c.red = 255;
    c.green = 128;
    c.blue = 0;

    std::cout << c.red << ',' << c.green << ',' << c.blue << '\n';

    return 0;
}

There were no arguments in this example of the rules for structure and class, no access specifier, no member functions, and no constructors. Adding any of these features to the above example is more suited to when an invariant is maintained:

#include <iostream>

class Fraction
{
// Members below this label are private
private:
    int numerator{};
    int denominator{1};

public:
    Fraction(int n, int d)
    {
        if (d == 0)
        {
            numerator = 0;
            denominator = 1;
        }
        else
        {
            numerator = n;
            denominator = d;
        }
    }

    void print() const
    {
        std::cout << numerator << '/' << denominator << '\n';
    }
};

int main()
{
    Fraction f{ 3, 4 };
    f.print();

    Fraction bad{ 1, 0 };
    bad.print();

    return 0;
}

The members num and den are private so we are not able to perform the same invalid action on them that we did above. The constructor takes the parameters and checks for invalid ones, in this case a denominator of 0.

From a language perspective, they are nearly synonymous again, a struct may have hidden members and class have public members. The compiler doesn't care. This is purely a signal to the human reader; in general, a struct is a data packet. A class is a sophisticated device.

A default access rule may also apply to inheritance but the same principle holds: a class of the wrong "type" inherited without a class or struct specifier makes a private inheritance and for a struct public. In everyday code, this rule is rarely visible as the programmer will write explicitly an access specifier there.

The rule is therefore a language and programmer one combined: the language specifies which is private, and if nothing is stated, struct's is public and class's is private. Use class to protect data, struct to openly present it.

Exercise: Struct Versus ClassStarter code, hints and solutionOpenClose
Guided practiceBeginner · 10–15 min

Practice: Struct Versus Class

Work through the mission, reveal hints only when needed, and compare your result with the model solution.

Your challenge

Create and print an additional Fraction representing 2/5. Explain the role of the explicit access labels and the constructor's denominator validation.

Mission checklist

  1. Run the starter as a baseline, then predict the change requested in Your challenge.
  2. Make that change in your editor; use the lesson-specific hints if you need help.
  3. Compile and run your answer, then compare with the worked solution and explain the result.

Starter program

This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.

#include <iostream>

class Fraction
{
// Members below this label are private
private:
    int numerator{};
    int denominator{1};

public:
    Fraction(int n, int d)
    {
        if (d == 0)
        {
            numerator = 0;
            denominator = 1;
        }
        else
        {
            numerator = n;
            denominator = d;
        }
    }

    void print() const
    {
        std::cout << numerator << '/' << denominator << '\n';
    }
};

int main()
{
    Fraction f{ 3, 4 };
    f.print();

    Fraction bad{ 1, 0 };
    bad.print();

    return 0;
}

Hints

Hint 1

This Fraction explicitly labels private and public sections. Changing class to struct would change default access only where an explicit access label is absent.

Hint 2

Construct another Fraction with numerator 2 and denominator 5. Use its public print member; explicit private labels still apply even if class were spelled struct.

Worked solution

A third Fraction is initialized to 2/5 and printed through its public interface. The explicit private/public labels preserve encapsulation regardless of the class keyword's default access.

#include <iostream>

class Fraction
{
// Members below this label are private
private:
    int numerator{};
    int denominator{1};

public:
    Fraction(int n, int d)
    {
        if (d == 0)
        {
            numerator = 0;
            denominator = 1;
        }
        else
        {
            numerator = n;
            denominator = d;
        }
    }

    void print() const
    {
        std::cout << numerator << '/' << denominator << '\n';
    }
};

int main()
{
    Fraction f{ 3, 4 };
    f.print();

    Fraction bad{ 1, 0 };
    bad.print();

    Fraction another{2, 5};
    another.print();
    return 0;
}

Example output

3/4
0/1
2/5

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.