Access specifiers restrict who can access a member. You can use an access specifier before a base-class name in a class definition and have access specifier labels within a class definition. The access specifiers are:
Anyone can access a public member.
Only the class, derived classes, and friends can access protected members.
Only the class and friends can access private members.
In a class definition, the default access for members and base classes is private. In a struct definition, the default is public. That is the only difference between a class and a struct, although by convention, some programmers use struct only for POD classes and use class for all other classes.
The access level of a base class affects which members of the base class are accessible to users of a derived class (not the derived class's access to the base class). The access level caps the accessibility of inherited members. In other words, private inheritance makes all inherited members private in the derived class. Protected inheritance reduces the accessibility of public members in the base class to protected in the derived class. Public inheritance leaves all accessibility as it is in the base class.
The access level of a base class also limits type casts and conversions. With public inheritance, you can cast from a derived class to a base class in any function. Only derived classes and friends can cast to a protected base class. For private inheritance, only the class that inherits directly and friends can cast to the base class.
Access level labels in a class definition apply to all data members, member functions, and nested types. A label remains in effect until a new access label is seen or the class definition ends.
Access specifier labels affect the layout of nonstatic data members. In the absence of access specifier labels, nonstatic data members are at increasing addresses within an object in the order of declaration. When separated by access specifier labels, however, the order is implementation-defined.
When looking up names and resolving overloaded functions, the access level is not considered. The access level is checked only after a name has been found and overloaded functions have been resolved, and if the level does not permit access, the compiler issues an error message. Example 6-23 shows how the compiler ignores the access level when resolving names and overloading.
class base { public: void func(double); protected: void func(long); private: void func(int); }; class demo : public base { public: demo( ) { func(42L); } // Calls base::func(long) void f( ) { func(42); } // Error: func(int) is private }; class closed : private demo { public: closed() { f( ); } // OK: f( ) is accessible from closed }; int main( ) { demo d; d.func(42L); // Error: func(long) accessibly only from base and demo d.func(42); // Error: func(int) is private closed c; c.f( ); // Error: private inheritance makes demo::f( ) private in closed. }
A derived class can change the accessibility of inherited members with using declarations. A derived class can restore the accessibility of a member whose access was reduced by protected or private inheritance, or it can increase the accessibility of an inherited protected member. Example 6-24 shows how using declarations work. (You can omit the using keyword, leaving only the qualified name, but such usage is deprecated.)
struct base { // struct is public by default. int x; protected: int y; }; // Private inheritance makes x and y private in derived1. class derived1 : private base { public: using base::x; // x is now public. }; // public inheritance menas x is public and y is protected in derived2. class derived2 : public base { public: using base::y; // y is now public. private: using base::x; // Pointless: x is still public }; int main( ) { base b; derived1 d1; derived2 d2; b.x = 0; // OK: x is public in base b.y = 42; // Error: y is protected in base d1.x = b.x; // OK: x is public in derived1 d2.x = d1.x; // OK: x is public in derived2 d1.y = 42; // Error: y is private in derived1 d2.y = b.x; // OK: y is public in derived2 return d2.y; }
A member's accessibility depends on how it is accessed. In particular, a protected, nonstatic member can be accessed only through its own class or a derived class, but not through a base class. Example 6-25 demonstrates this principle.
class base { protected: int m; }; struct derived : base { void reset( ) { this->m = 0; // OK } void set(base* b) { b->m = 0; // Error: cannot refer to m through base } bool operator==(const derived& d) { return this->m == d.m; // OK } };