data members as "protected"?

A

Alf P. Steinbach

* Cy Edmunds:
[snip]
A derived class (on the one hand) and so-called client code (on the other
hand) are two different kinds of client.

A derived class is a fully trusted client: you're giving it access to your
innards, and if it screws up then you (or it) is dead anyway.

A "trusted client"? That's where we disagree. Sure, over the next few months
you and your teammates will probably all be there and understandings and
agreements you have in place will work OK. But as time goes by people come
and go

Consider that you will perhaps not ever know who is deriving from your
class.

Consider that you're writing library code.

The trust relationship has nothing to do with project teams.


If your code is to still be maintainable with a new set of players
my advice is: don't trust anybody!

That's a bit paranoid now, isn't it? I think the code should be maintable
with a any set of players. I don't think one should need to know them.
 
D

DaKoadMunky

The whole point of object
oriented programming is to isolate the interface from the implementation,
but if every data item is required to have a get/set pair, who are you
kidding? I mean, why is that a lot better than public data?

class Point
{
public:

Point(int x,int y);

int GetX() const;
int GetY() const;

void SetX();
void SetY();

private:

int x,y;
};

This class separates its interface from its implementation. I can change how
and where I store the x and y members without affecting how clients read and
write those members.

However, it also has getters and setters corresponding to every data item.

Would you consider this dubious?

A number of years ago in this forum the topic of getters and setters was
discussed and a popular author used the term "visible state." If a particular
data item is part of the "visible state" of an object than it may be perfectly
acceptable to have a getter and or a setter so long as those functions are
written in ways that don't create implementation dependencies (typically
returning by address.)

Regards,
BFS
 
C

Cy Edmunds

DaKoadMunky said:
class Point
{
public:

Point(int x,int y);

int GetX() const;
int GetY() const;

void SetX();
void SetY();

ITYM void SetX(int); and void SetY(int);
private:

int x,y;
};

This class separates its interface from its implementation.

A little.
I can change how
and where I store the x and y members without affecting how clients read and
write those members.

OK, I'm game. Show me your alternate implementation.
However, it also has getters and setters corresponding to every data item.

Would you consider this dubious?

I have written classes pretty much like this one but I didn't include the
set functions. After all, you can always change the state with the
constructor:

Point p1(2, 3);
....
p1 = Point(3, p1.GetY());

A good optimizing compiler should get this right, and the abstraction of a
Point as a pair of numbers (rather than just two numbers) is emphasized.
Plus without the set functions I can use a simpler notation:

class Point
{

private:
int m_x;
int m_y;
public:
Point(int i_x, int i_y) : m_x(i_x), m_y(i_y) {}
int x() const {return m_x;}
int y() const {return m_y;}
};

making the example above

p1 = Point(3, p1.x());

None of this is a big deal, but it does show that it is not usually
necessary to automatically include get/set pairs for every data item.
A number of years ago in this forum the topic of getters and setters was
discussed and a popular author used the term "visible state." If a particular
data item is part of the "visible state" of an object than it may be perfectly
acceptable to have a getter and or a setter so long as those functions are
written in ways that don't create implementation dependencies (typically
returning by address.)

Yeah, returning an address or a reference would really clobber
encapsulation.
 
C

Cy Edmunds

Alf P. Steinbach said:
* Cy Edmunds:
[snip]
My take on this is that whenever you use the keyword "protected" you are
expecting your client to derive from the class. In such a case I
don't
see
why protected data is any better than public data -- either way you are
giving your client full access to your implementation details.

A derived class (on the one hand) and so-called client code (on the other
hand) are two different kinds of client.

A derived class is a fully trusted client: you're giving it access to your
innards, and if it screws up then you (or it) is dead anyway.

A "trusted client"? That's where we disagree. Sure, over the next few months
you and your teammates will probably all be there and understandings and
agreements you have in place will work OK. But as time goes by people come
and go

Consider that you will perhaps not ever know who is deriving from your
class.

Consider that you're writing library code.

The trust relationship has nothing to do with project teams.

Hm. I thought this was *my* argument. I guess we agree after all.
That's a bit paranoid now, isn't it? I think the code should be maintable
with a any set of players. I don't think one should need to know them.

Exactly. So why did you say:

"A derived class (on the one hand) and so-called client code (on the other
hand) are two different kinds of client."

Since you "don't think one should need to know them" you have no basis for
considering them "different kinds of clients". Right?

Let summarize my position. The same basic information hiding techniques are
available to the C++ programmer for public and protected sections of the
class. These techniques have the effect of increasing maintainability, which
as I'm sure we agree, is highly desirable. As you say, "you will perhaps not
ever know who is deriving from your class". Hence my claim that failure to
apply these techniques is a bad idea in either the protected section or the
public section.
 
A

Alf P. Steinbach

* Cy Edmunds:
Hm. I thought this was *my* argument. I guess we agree after all.
Heh.



Exactly. So why did you say:

"A derived class (on the one hand) and so-called client code (on the other
hand) are two different kinds of client."

Since you "don't think one should need to know them" you have no basis for
considering them "different kinds of clients". Right?

Nope. When someone decides to derive from your class they require more access
to things, paying for that in part by taking on more responsibilities. They
might do so without actually taking on the associated responsibilities. But
that's then their problem, not yours. When they decide to reinterpret_cast a
pointer to an object of your class they require even more access, taking on
even more responsibilities (which again they could fail to actually do)...

Your responsibilities & support for the different usage scenarios are
correspondingly different.

For the case of ordinary "external" usage: protect the client. For the case
of derivation: support what derivation would naturally be used for that is in
addition to what could be achieved by "external" usage, while protecting the
client to the degree that is practical without unduly restricting the client.
For the case of reinterpret_cast and such: no responsibilities as class
designer, except in very-high-security scenarios (where you might consider
encryption of member data, access-restricted OS memory, and such).

Let summarize my position. The same basic information hiding techniques are
available to the C++ programmer for public and protected sections of the
class. These techniques have the effect of increasing maintainability, which
as I'm sure we agree, is highly desirable. As you say, "you will perhaps not
ever know who is deriving from your class". Hence my claim that failure to
apply these techniques is a bad idea in either the protected section or the
public section.

I would agree with a literal interpretation of that. However, also consider
failure to _not_ apply data/functionality hiding, i.e. failure to expose to
derived classes. Since MFC is my favorite repository of bad-design examples
(happily it's been years since last I had to use it...), one infamous case was
the print preview feature, where the only way you could override
functionality, access data and so on -- I forget exactly what, and mention
that lapse of memory because some few things actually were supported -- was
to copy the MFC source code and roll your own version...
 
D

DaKoadMunky

This class separates its interface from its implementation.
A little.

A little?

I would say completely.

Clients are dependent upon an interface for getting and setting the x and y
coordinates and the interface signature does not allow clients to become
dependent upon implementation details.

The typical objection I have been exposed to with my example class or classes
like it is the following...

"Well, you are storing member y as an int and your accessor GetY() returns an
int! Your function is just a public data member in disguise!"

If my accessor returned by reference I would agree.

However, my accessor returns by value.

It is just a happy coincidence that the type I return is the type I store.

If for some reason I decide to change how and/or where x and y are stored I can
do so knowing that clients will not need to be rewritten (so long as I am not
changing the meaning of x and y with respect to their conceptual type, which in
this case is int.)
OK, I'm game. Show me your alternate implementation.

I wish I had a better example. I can't think of any reason why I would really
need an implementation other than the one that I originally gave.

The point (no pun intended...honest) is that if I want to change it (maybe to
use dynamically allocated ints, maybe to use an std::pair<int,int>, maybe to
store x and y in a textual representation, maybe to write to a file) that I can
do so without forcing clients to be rewritten because clients are dependent
only upon the interface.

Maybe the Point class is not the best way to illustrate it because it is hard
to imagine any useful alternate implementation.
None of this is a big deal,

The fate of the free world depends upon it :)
but it does show that it is not usually
necessary to automatically include get/set pairs for every data item

Agreed. I would never automatically include getters/setters for every data
item. Every data item needs to be analyzed and a decision made as to whether
or not that data item is part of the visible state of the object. If it is
visible is it visible for reading, writing or both? Then the getter/setter
needs to be written in a way that does not allow implementation details to leak
out.

BFS
 
H

Howard

I make no claim that every data item requires a get/set pair. Only data
items whose value needs to be known by a client (such as Point.x and
Point.y) require exposure at all. Making them public is fine, if there are
no internal state requirements on those values that a client might
accidently break (note "accidently", not "maliciously"). But if there *are*
strict internal requirements, such as range-checking or side effects that
need to occur when setting, then a setter is a god way to accomplish that.

BTW, your idea of a constructor to manipulate state is, in my opinion, not
very realistic, except perhaps for POD structs (where I'd be likely to use
public data anyway). Especially when using RAII techniques! There may be a
whole slew of activity going on behind the scene, allocating resources you
shouldn't need to know about as a client. It might be quite expensive to
allocate a whole new object, and in some cases may not even be possible
(esp. if a resource allows only single-user access at one time).
ITYM void SetX(int); and void SetY(int);


A little.


OK, I'm game. Show me your alternate implementation.

The Point example doesn't lend itself well to an alternate implementation.
That kind of struct is usually just POD, with public data. But there are
certainly many cases where what appears to be a simple data member may in
fact have some dependencies on other data, may be calculated, may have side
effects when changing, etc. These are cases where getters and/or setters
may come into play. It should be obvious of course that these facts
themselves would indicate to you as a designer whether a getter and/or
setter is needed or not.

In practice, if I'm writing a POD struct, I rarely have any methods at all.
Perhaps a constructor, and an operator or two. But for most of my classes,
everything goes into private data, until I find that there are (or one day
may be) clients that need access. If the clients are derived classes, I
then consider whether to move the data to protected, or to put in a
protected getter and/or setter, as the situation calls for. If the clients
are external, the same considerations need to be made, only with respect to
public visibility.
I have written classes pretty much like this one but I didn't include the
set functions. After all, you can always change the state with the
constructor:

Point p1(2, 3);
...
p1 = Point(3, p1.GetY());

A good optimizing compiler should get this right, and the abstraction of a
Point as a pair of numbers (rather than just two numbers) is emphasized.
Plus without the set functions I can use a simpler notation:

class Point
{

private:
int m_x;
int m_y;
public:
Point(int i_x, int i_y) : m_x(i_x), m_y(i_y) {}
int x() const {return m_x;}
int y() const {return m_y;}
};

making the example above

p1 = Point(3, p1.x());

None of this is a big deal, but it does show that it is not usually
necessary to automatically include get/set pairs for every data item.

I agree absolutely. The current and future requirements of the system
should determine the design, not some hard-and-fast rules.

-Howard
 
H

Howard

After some more reading, looking at my own code, and seeing some of the
responses here, I'm going to "revise and extend" my remarks a little. I
agree with Mr. Steinbach one one key point, and that is that a derived class
is not really a "client" of a base class at all. At least when using public
inheritance, a derived class, by definition if you will, IS A base class.
Therefore, anything that the base class can do with its members, the derived
class ought also be able to do, without restriction.

Looking at my own class hierarchies, where I have a class that is truly
a client of a second class, invariably the second class is either external
to the first one (with a pointer or reference to the second passed as a
paremeter or stored as a member of the first), or the second class is
contained within the first class as a member itself. This is true "client"
behavior, where one class is asking another to maintain data and perform
tasks for it.

Wherever I have derived classes, it is because the derived class is
simply a specialized or enhanced version of the base class. It is not
asking the base class to do some of its tasks, and doing the rest itself
(although it may seem that way - from a source code point of view - in the
fact that some functions need not be overridden when the base class actions
are sufficient). Rather, it can be thought of as a special case of the base
class itself (such as a MusicEffect class as opposed to just an Effect
class). In that sense, all the actions that take place in what are (in
source code) the base class' member functions, are IN FACT taking place in
the derived object itself, except that behind the scenes there is an
indirection through the vtable (or whatever) to the base class' function
address space. The derived class does not call the base class...the base
class code is executed because the derived class IS A base class.

Given this, I see no reason why making data protected would be wrong, in
cases where the class may be derived from. Making a protected getter or
setter would, in my (updated) opinion, not make much sense for the purpose
of data hiding, because you don't hide data from YOURSELF! I would still
use a getter/setter for cases where the underlying data requires
manipulation or calculation, or creates side-effects when modified, but in
those cases I'd use the getter/setter even WITHIN THE BASE CLASS.
(Otherwise, I'd be requiring myself to write duplicate code everywhere I
read or wrote a member data value.)

Regarding the "don't trust anybody" thought, it is not the
responsibility of the writer of a base class to maintain integrity of that
class if the writer of a derived class desires to write code that changes
the expected internal state of an object of that class. Remember, an
instance of a class is not a class. It's an object, and if someone wants to
write a class whose instances don't work properly, the fact that it is
derived from some base class you wrote is totally irrelevant. Protecting
the state of an instance of the base class from external clients is fine,
because otherwise instances of your base class itself may not perform as
expected. But protecting it from a derived class' actions makes no sense.
Your base class will still function if some moron screws up his derived
class. And even if you consider that there may be an existing third object
or container that depends upon proper behavior of any base or derived
version of your objects, you're still covered, because it's the new stuff
that moron writes that will break, not your stuff, and it's definitely his
responsibility for him to write correct code, not yours. If you need to
protect your work, and anything that depends on your work as well, then
don't allow joe schmoe to derive from your class in the first place. Make
him contain or call instances of your class. Then your code is safe, even
from morons.

-Howard
 
D

David Hilsee

[...]
Given this, I see no reason why making data protected would be wrong, in
cases where the class may be derived from. Making a protected getter or
setter would, in my (updated) opinion, not make much sense for the purpose
of data hiding, because you don't hide data from YOURSELF! I would still
use a getter/setter for cases where the underlying data requires
manipulation or calculation, or creates side-effects when modified, but in
those cases I'd use the getter/setter even WITHIN THE BASE CLASS.
(Otherwise, I'd be requiring myself to write duplicate code everywhere I
read or wrote a member data value.)
[...]

I think this is getting a bit too philosophical. The derived class is not
the base class. If the base class wants to hide something from the derived
class, it is not hiding anything from itself; it's hiding it from the
derived class. Yes, the derived class derives from the base, and
programmers occasionally use "is a" as slang to describe such a
relationship, but that doesn't mean that the derived class has any right to
depend on the implementation of the base. Information hiding may help later
on if the base class needs to change some of its implementation details,
because the derived classes do not have to be modified if it never depended
on it.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,176
Messages
2,570,950
Members
47,503
Latest member
supremedee

Latest Threads

Top