Variable Exposure - Epidemic Alert

Started by
35 comments, last by Holy Fuzz 19 years, 8 months ago
I've been coding in C++ for 4 or 5 years now, and I have shamelessly used public member variables in every class I've ever designed. I'm trying to change my horrible habits.
Quote:Never use public or protected member data.
This is something I ran into, within a list of programming guidelines for OO decency. Why not have member data as protected? Do classes publically derived from base classes inherit the protected data? What about the private data? I know I sound like an idiot, but I've never attempted to understand the issues around protected and private declaration, or the difference between them. Are there any good tutorials or documents out there specifically written to help educated but corrupted programmers? :) Here's a scenario for ya. Say I have a class that is written correctly (term for non-exposure?). If I include that class as a member variable in another class, can I expose that data? It's own data is hidden from access, so why hide the actual class member? Let me know if you don't follow what I'm describing. Here's another. What if I have a GUI system which doesn't have it's own data, but uses pointers to the actual runtime data to do its work? How can I respectably give a generic GUI control total acces to modify a data member (generic variable, such as int, float, char, etc) within a class which has that data as protected / private? Supplying a pointer to it would completely destory the whole point, right? I have other questions, but I don't want to be a leech :) The more simple your terms of are, the higher I'll rate ya ;) Thanks for any advice.
Advertisement
First of all, whenever a programming text says "Never do XXXX", it usually means "XXXX is generally considered a BAD thing for a variety of reasons, but is useful in some circumstances". Remember these are guidelines you're reading, not hard and fast rules that you must follow.

Anyway, in most practical cases, the superclass will either be an interface class - in which case it doesn't require any data at all - or a fully self contained class in it's own right - in which case the subclasses can access/manipulate the data in the same way that everything else does, through the use of suitable member functions.

In your first scenario, I suppose you could expose it directly, but it's still not recommended, since the parent class may need to perform initialization, or other operations on it. Strictly speaking it would be better to use inlined accessors or else rethink your design.

I'm not sure I fully understand your second scenario, but again it sounds like something which could be resolved with a rethink of your implementation.
Quote:Original post by Jiia
Why not have member data as protected? Do classes publically derived from base classes inherit the protected data? What about the private data?


I think it would be best to describe why we encapsulate a type's representation from other unrelated types/modules using private access in the first place before talking about why it's best practice not to use protected data members.

The two main reasons why we encapsulate a type's representation is to:

1. Provide loose coupling among modules, meaning that if a library of modules/types relies on another module/type's representation and we decide that the representation for it is incorrect/unsuitable and we make changes in that module/type then every other module/type needs to be updated. Where as if they all relied on the interface of the module/type instead it's easy to change the repsentation without effecting other modules/types.

2. To maintain the state invariant, meaning to avoid putting an instance of some type in an underfined state. If the data members are only allowed certain values in a certain ranges and you allowed direct access how could you stop users of that instance from putting invalid/out-of-range values?

Now we know the main reasons why we encapsulate a type's representation from other unrelated types/modules we'll talk about protected members.

If you make a data member protected your basically contradicting your self, why? well you've encapsulated from unrelated types/modules but you allow clients direct access from sub-types and now instead of avoiding tight coupling among unrelated types you've just made tight coupling with related types and the possiablity of invalidating the state invariant making an instance of the sub-type in a undefined state.

Probably see where this is going, a type's representation is just minor details, it's interface is more important, this is especially true for abstract data types (ADT) where they can have more than one representation for example the ADT Stack there are probably infinite number of ways to represent the internals of a stack but no one cares we just care what we can do to stacks, it's behaviour/operations/interface.

Quote:Original post by Jiia
Here's a scenario for ya. Say I have a class that is written correctly (term for non-exposure?). If I include that class as a member variable in another class, can I expose that data? It's own data is hidden from access, so why hide the actual class member? Let me know if you don't follow what I'm describing.


I don't follow please elaborate more clearly.

Quote:Original post by Jiia
Here's another. What if I have a GUI system which doesn't have it's own data, but uses pointers to the actual runtime data to do its work? How can I respectably give a generic GUI control total acces to modify a data member (generic variable, such as int, float, char, etc) within a class which has that data as protected / private? Supplying a pointer to it would completely destory the whole point, right?


Well first you should prefer returning constant references over pointers but anyways provide a constant member function that returns constant pointer or constant reference.

[Edited by - snk_kid on August 3, 2004 11:45:43 AM]
You make data private for a reason - its status is important and you need to control access to it so that it doesn't become invalid. Anyone outside of the class shouldn't have to worry about the object being in an invalid state.

These reasons still apply to protected data. You're saying 'only derived classes should have access to this data - anyone else might modify it in an inconsistent way'. But if there is that danger, derived classes are just as capable of messing up the state of the object. So it's a bit of a half baked idea.

Bjarne Stroustrup (the language's inventor) had this to say about it:
Quote:I think that overstates the case against `protected' a bit. here is the actual quote:

Five years or so later, Mark banned the use of protected data
members in Interviews because they had become a source of bugs:
``novice users poking where they shouldn't in ways that they ought
to have known better than.'' They also seriously complicate
maintenance: ``now it would be nice to change this, do you think
someone out there might have used it?'' Barbara Liskov's OOPSLA
keynote gives a detailed explanation of the theoretical and
practical problems with access control based on the `protected'
notion. In my experience, there have always been alternatives
to placing significant amounts of information in a common base
class for derived classes to use directly. In fact, one of my
concerns about `protected' is exactly that it makes it too easy
to use a common base the way one might sloppily have used global
data.

Fortunately, you don't have to use protected data in C++;
`private' is the default in classes and is usually the better
choice. Note that none of these objections are significant for
protected member functions. I still consider `protected'
a fine way of specifying operations for use in derived classes.


The Design and Evolution of C++, sec13.9.

However, I do consider reliance on protected data a dubious practice in any language.


I was recently reading Large Scale C++ Design by Lakos and was interested by his practice.

Basically, the main use of protected is for member functions and it allows you to call them in a controlled manner. This is useful (primarily in my opinion) for the Template Method Pattern. But Lakos considers it to be putting implementation details in the public interface of the class. If I'm a user of the class I don't care what its protected functions are. I'm not allowed to call them.

So Lakos suggests you could move those functions and put them in an implementation class (this is something he does all the way through and it is now well known as the PimplIdiom). But this is a specific application of it to remove protected members from the public interface. So the protected functions go into another class, but you make them public. Then people derive from that class instead of the original one.

In terms of good design, on the whole, making your interface classes have data protected or even private isn't enough. You need to remove the details completely.

//SomeClass.hclass SomeClass {private:    std::string m_name;public:    void SetName(const char* name) {        m_name = name;        OnSetName(name);    }protected:    virtual void OnSetName(const std::string& name) = 0;};

becomes
//SomeClass.hclass SomeClassImpl;//will be the private functionalityclass SomeClassStrategy;//will be the protected functionalityclass SomeClass {private:    SomeClassImpl* m_pImpl;public:    void SetName(const char* name);    void SetStrategy(const SomeClassStrategy*);};

You implement SomeClassImpl elsewhere. Users can derive from SomeClassStrategy. All you have in the public interface is the public members. You have decoupled the implementation details and can change them without users of having to recompile.

Hope that all makes sense. I've rambled a bit, but basically, you can remove protected and private data and functions from your class interface. The data becomes private and the functions become public. The implementation is never made available to users.

This is useful and it is good to understand the benefits. These are rules to do with good large scale design. It isn't morally wrong but could be a design problem. Never say never though.
You know, at the heart of this question is the question "Who are we designing our classes for?"

Often the answer to this is ourselves and therefore the whole data hiding thing seems silly because we can't hide it from ourself because we know it's there and we know the bounds and limits and uses of that data, so when we our the class writer and user it seems to add a lot of unneeded redirection.

So I guess the bottom line to ask yourself when you are writing a class is who will be the end user? If you are sure it's only for yourself then I wouldn't worry about hiding and continue using public members.

On a related note though, hiding from yourself may be benificial too if you ever think you might have to come back to a complicated class you havn't worked with in a long time. Though, since you've got the source, you can always go back in and review/relearn it.

D
Anti-Sig: Do Not Read This Signature
Quote:Original post by darrylsh
You know, at the heart of this question is the question "Who are we designing our classes for?"

Often the answer to this is ourselves and therefore the whole data hiding thing seems silly because we can't hide it from ourself because we know it's there and we know the bounds and limits and uses of that data, so when we our the class writer and user it seems to add a lot of uneeded redirection.

So I guess the bottom line to ask yourself when you are writing a class is who will be the end user? If you are sure it's only for yourself then I wouldn't worry about hiding and continue using public/private.

On a related note though, hiding from yourself may be benificial too if you ever think you might have to come back to a complicated class you havn't worked with in a long time. Though, since you've got the source, you can always go back in and review/relearn it.

D


And the question is why are we creating a user-defined types for are selfs? The common answer is to build a portfolio of are skills to show it to employers to get a job so do we really wanna show sloppy code that no-one can decipher/decrypt?

I'm not saying that these good principles apply for every-case, like a 2d vector/point is valid to have it's members public because there is no limit/range on the values the members can have, the representation is never gonna change.
Quote:Original post by darrylsh
You know, at the heart of this question is the question "Who are we designing our classes for?"


The point of encapsulation is not so much to hide implementation details from another programmer, but to ensure that classes are self contained and loosely bound to one another, resulting in modular, reusable code which can be expanded without necessarily breaking the code that depends on it.

Of course, properly encapsulated reusable code is not something you'll learn to write overnight, and there may be projects for which it simply isn't worth the extra effort. It's very easy to get caught in the trap of overengineering everything, with the result that instead of getting unreusable but functional code, you end up with no working code at all.
Quote:Original post by darrylsh
You know, at the heart of this question is the question "Who are we designing our classes for?"

Often the answer to this is ourselves and therefore the whole data hiding thing seems silly because we can't hide it from ourself because we know it's there and we know the bounds and limits and uses of that data, so when we our the class writer and user it seems to add a lot of unneeded redirection.


Taken to an extreme you could say you'll have all data public in your classes/structures and no member functions. You could make all your data global too.

But your design would be unmanageable.

The reason you write code in a professional manner isn't because someone is looking over your shoulder, or even because some prospective employer may someday see it. You write good code because it's good code.
I really appreciate all of the help and advice. I've learned more in the last few minutes than in the last few months. Kind of sad, really.

Quote:Original post by petewood
These reasons still apply to protected data. You're saying 'only derived classes should have access to this data - anyone else might modify it in an inconsistent way'. But if there is that danger, derived classes are just as capable of messing up the state of the object. So it's a bit of a half baked idea.

Ahh, I very much like the simplified example. This was very helpful. So all derived classes still inherit all member data variables, no matter which section they are listed under? But the point is that in private, the derived class cannot modify it's base classes' data, just as the outside world cannot modify it? But protected is still very useful to include tasks for that class to run on itself. This really does make a lot of sense.

My current game has 1.46 megs of poorly written source code, so I'm gonna be busy for quite a while.

I don't seem to use pure interface classes very often. My interface classes usually contain data which is needed for all derived objects. Such as CObject, which has a position vector, which is needed by CCharacter and CItem. That's a simple example; the number of actual variables is pretty high. My code is written now in such a way that CObject has member functions which handle almost everything that both characters and items need to have handled. There are very few functions which are overridden. Most are virtual, and the character and item classes use many CObject member functions to perform their tasks.

There are some situations where not having data as public or protected would seem to be a real pain. My CObject class has a CBitflag variable. CBitflag is a correctly encapsulated class that has member functions to handle tasks with it's own bitflag variable. How do I allow characters to turn on and off state-flags without having to write the CBitflag class interface into the CObject class interface? Example:
class CBitFlags{public:    CBitFlags() { Clear(); }    VOID Clear()                    { Data = 0; }    VOID Fill()                     { Data = 0xFFFFFFFF; }    BOOL Bits(ULONG FlagValue)      { return Data & FlagValue; }    VOID TurnOn(ULONG FlagValue)    { Data |= FlagValue; }    VOID TurnOff(ULONG FlagValue)   { Data &= ~FlagValue; }    VOID Toggle(ULONG FlagValue)    { Data ^= FlagValue; }    ULONG GetData() { return Data; }    operator = ( CONST ULONG FlagValues ) { Data = FlagValues; }private:    ULONG        Data;              // Flag Data};class CObject{public:    ...private:    CBitFlags   FStatus;    ...};class CCharacter : public CObject{public:    VOID TakeHealth(INT value)    {        HurtMe(value);        if(GetHealth() < 0)            FStatus.TurnOn(STATUS_DEAD);    }};

There are many flags defined for FStatus. Well... 32 :) And this is just an example; I know I could allow CObject to handle deaths, but this is not the case with all flags. And I cannot access them from CCharacter if FStatus is private. What can I do?

I almost forgot to ask one other question. Why derive classes as public? As in CCharacter : public CObject. What happens when you derive them private or protected? Again, sorry if this is a dumb question.

Thanks again.
Quote:Original post by Jiia
But protected is still very useful to include tasks for that class to run on itself. This really does make a lot of sense.


I'm not sure what your saying here.

Quote:Original post by Jiia
I don't seem to use pure interface classes very often. My interface classes usually contain data which is needed for all derived objects. Such as CObject, which has a position vector, which is needed by CCharacter and CItem. That's a simple example; the number of actual variables is pretty high. My code is written now in such a way that CObject has member functions which handle almost everything that both characters and items need to have handled. There are very few functions which are overridden. Most are virtual, and the character and item classes use many CObject member functions to perform their tasks.


Maybe your using the wrong levels of abstraction, i see having CObject type which is the root of all types in a type hierarchy not very useful at all to be honest, it's to abstract to be put into any good use, you end up stuffing data and pushing functionality there where it doesn't actually make any sense.

You usually find interfaces in the concepts of the problem domain, where you see adjective-nouns such as serializable, cloneable, renderable etc or in traditional object-oriented abstract data types (ADTs). Interfaces do not have data members, constants are fine though.

Quote:Original post by Jiia
There are some situations where not having data as public or protected would seem to be a real pain. My CObject class has a CBitflag variable. CBitflag is a correctly encapsulated class that has member functions to handle tasks with it's own bitflag variable. How do I allow characters to turn on and off state-flags without having to write the CBitflag class interface into the CObject class interface? Example:
*** Source Snippet Removed ***
There are many flags defined for FStatus. Well... 32 :) And this is just an example; I know I could allow CObject to handle deaths, but this is not the case with all flags. And I cannot access them from CCharacter if FStatus is private. What can I do?


solution look at the standard library bitset and prefer using that instead

This topic is closed to new replies.

Advertisement