Enscand, Inc.


Access Modifiers in C++/C#/Java

One of the most basic aspects of understanding object-oriented (OO) design is the use of access modifiers to define the accessibility of your methods, members, operators, etc. By access modifiers, I mean the keywords

public protected private
which exist in C++, C# and Java. Despite the relative simplicity of this concept, it continues to be misunderstood, at the cost of code modularity and reusability.

As background, we'll review the meaning of the aforementioned access modifiers as they apply to methods, constructors/destructors, members, and operators. (the application to inheritance is not explicitly covered here .. if you don't know what that means, consider yourself fortunate!) The public keyword indicates that any users of a particular class have access to those methods, members, etc. declared to be public. The private keyword indicates that an element may only be used by the class in which it is declared (we're neglecting friends, which don't exist in all languages). The protected keyword is somewhere in between, and provides the most confusion amongst developers. Protected elements may be used by the class in which they are declared, and any classes which derive from that class. However, protected elements are not accessible to other code, which does not inherit from the element's parent class.

Default Guidelines

The guidelines developers should prefer (caveats will be discussed later) for using these three access modifiers are as follows:

  • By default, member data should be declared private. Any access to those members that is needed by the outside world should be provided via public methods, which wrap (and control access to) the private members.
  • Methods that you know are needed by other classes, which don't derive from your class, should be marked public. The rest of your methods should be marked protected.
  • A common exception to the previous guideline is that methods that you do not want anyone to call should be declared private. In C++, you can declare a private method, and not even provide the implementation for it, if you know that no code (not even that class itself) will ever need to call the method. This is often done for the copy constructor and assignment operator in code where copying objects is discouraged (in favor of handling objects by reference, rather than copying).
  • For readability, it's generally a good idea to declare your elements in the order: public, protected, and private (from the top to bottom of your source file). This is more of a stylistic preference, but does help keep code well-sorted.

  class MyClass
  {
    public:
      MyClass() {
         m_initialized = false;
      }

      int foo() const {
         return m_something * 10;
      }
      float getSomething() const {
         return m_something;
      }
    protected:
      void setSomething(float value) {
         m_something = value;
         m_initialized = true;
      }
      bool isInitialized() const {
         return m_initialized;
      }
    private:
      MyClass(const MyClass&);
      MyClass& operator=(const MyClass&);
   
      float m_something;
      bool  m_initialized;
  }

Rationale

There are a couple common complaints with the previous set of access modifier guidelines. The first is the restriction on public and protected member data. If you want to use public member data, then you should probably be using a struct, not a class. You can always provide public access to a class's private data with public accessor methods (a.k.a. "getters") if you need to. If performance is a consideration, then you should consider using C++ inlining for your getter methods. This alleviates much of the (small) performance impact of keeping methods private, and providing public getters. If that's still too much of a performance hit, you need to host your application on a more powerful computer/device. Really, it's the year .

When you look at profiling your application, there's generally a 80/20, or 90/10 rule that applies. In other words, the vast majority of your performance bottlenecks will be in a small section of your code: perhaps an inefficent math algorithm, or excessive disk I/O. The small cost of using inlined getter methods will very rarely even be a blip on your performance profiling report. If you aren't using C++, and thus don't have inline, then you really shouldn't be concerned about this: if you can't afford such a small cost, you can't afford to use C# or Java. Besides the fact, that if you are using C# or Java, you almost certainly are using an IDE that will generate the getter (and/or setter) method code for you with a couple mouse clicks. So, the cost of writing these simple methods is even minimized. If you're writing C# or Java code with a text editor, shame on you!

The reason that using getters to wrap access to your data is so important is one of the fundamental tenets of OO Programming: encapsulation. Encapsulation provides an isolation between implementation (the member itself), and interface (the getter method). If a member name needs to be changed for readability, it can be done without breaking client code, which only depends on the public methods. If thread-safety is an issue, you can implement mutex locking inside each method without affecting clients.

   int getWidgetNumber() 
   {
     SCOPE_LOCK(m_lock);     // aside: this uses scope-based locking in case you wondered
     return m_widgetNumber;
   }
You should certainly never let client code be responsible for locking data in your class. This should always be encapsulated. Encapsulation also allows the author of the class to determine whether clients can access data by reference, or only by value. If a piece of member data is read-only, then it might make sense to provide a getter method that actually returns a reference to the member (should be a const& in C++). This avoids mandating unnecessary copies, but the caller may always make a copy if they wish. For most member data, the getter method should return by value, which enforces that the caller cannot hold a reference to the class's internal member data. The point is, the author of the class is in a much better position to make this decision, than are the users of the class. The responsibility for that knowledge is encapsulated where the data is defined, which is the way encapsulation is supposed to help you write better code.

Another complaint with these guidelines is that people want to make their non-public methods private, instead of protected. The rationale given is that they don't trust other developers and don't want them using these non-public methods. First of all, I will mention that it's sometimes necessary to write library code that you release to other developers, but ones who are not "trusted". In fact, you may be writing a jar file to be distributed on the web, as an interface to your company's systems. Sometimes, Java final and C# sealed can help you in those situations. However, there may be circumstances where a class must be made extensible, but your method is too critical to allow it to be used out of its original context. In that case, by all means, make the method private. Remember, this is a set of default guidelines. But, the important point is that non-public methods should be made protected the vast majority of the time.

The problem with getting into the habit of making methods private is that you are inhibiting reuse, which is inherently non-OO. You can't universally discourage reuse because the developer in the next cubical might screw up your perfect code. That's throwing the baby out with the bathwater. Of course, it's possible that protected methods wind up causing problems. But, private methods can, too. If your methods aren't available, other developers are encouraged to rewrite your logic in their own method. That leads to increased development cost, and increased maintainability costs. Instead of having two developers using (and thus testing) one method twice as much, you have each developer testing their separate implementations half as much. Your code becomes less well tested, harder to maintain, and harder to understand for other developers, who have no idea why the same code is written twice. With the advent of smart IDEs, which help you see what methods are available by simply starting to type, it's even more likely that another developer will fail to notice your existing private method. People are using the actual source code less and less to determine what a class's API is (one reason that C# and Java developers don't usually miss having headers).

Another logistical issue is that if a developer is keen enough to realize that a base class already has the method they need, but it's currently private, making that small change to make the method protected may be difficult. The base class may be in a 3rd party library, in which case you can't change the code at all. Or it may be in a piece of code that's strictly configuration controlled. So, to change one little keyword from private to protected, you may have to write a change request, jump through flaming hoops, ask 10 peoples' permissions, and finally struggle with the crummy version control tool your organization uses. All because another developer didn't have the foresight to imagine that anyone would ever have a good reason to extend his code.

Don't confuse the popular eXtreme Programming mantra "you're not going to need it!" with making non-public methods private until a new subclass proves a need for it to be protected. This mantra is addressing the tendency to actually write extra code just in case somebody uses it down the road. That is an issue, and I agee with XP on that issue. However, making a method protected instead of private doesn't involve writing more code, and thus has no cost to provide for future growth.

So, do everyone a favor. Stop pretending that Object-Oriented Programming is a fad that will go away. Or that your system is so finely performance-tuned that you can't afford a single extra machine instruction. Use access modifiers properly, and get rid of all those cassette tapes in your car while you're at it!



Copyright © 2003-2024 Enscand, Inc.
All Rights Reserved