Read-only, as opposed to const member

H

Howard

Julie said:
No, you create a new or derived class.

But then you break everyone's code that uses your original object! They now
have to change their code to use your new object, whereas if you just
returned the computed value from the accessor, the user of your class never
even has to know how that value was computed, how it was stored, or
anything. They never even know the underlying accessor code was changed at
all. They get the value they want when they ask for it, and that's all they
should care about.

Experience has taught me this well. In writing database-access programs, I
often had to change something on the back end, and I was in general
*required* to not change the interface. When the user wants the trip
mileage, they never need to know if that was a stored value, or if it was
computed via a third-party mileage lookup. WIth thousands of users of our
system spread across the country, it was *very* important to make these kind
of changes as invisible to the users as possible. (It often took at least a
month to propogate changes to users when we had to make an interface change,
and we had to in effect maintain two system during such transitions...what a
pain!)
You have now just changed the behavior of the original function with respect to
performance, which is a very real and tangible change.

Only in the internal behavior of the object, not in the interface or the
contract to provide the requested value.
This is why I advocate const reference member accessors to simple data -- it
*guarantees* that access to the underlying data is immediate and absolute, and
will stay that way. It doesn't break encapsulation as some have said, but
creates an immutable contract between the class designer and class user. You
can't get that w/ functions.

I don't understand this. Why is a guarantee to "immediate and absolute"
access to the underlying data a requirement? What does the user of the
class care how the data is stored, represented, obtained or calculated? The
contract is to provide the value asked for, not (in general) a specification
of how that value is obtained or stored.

If you have some special requirement to actually see the data, as it is
stored (it that's even possible...sometimes you *have* to calculate it!),
then that's a totally separate need that's not part of the OP's question,
and only relevant to the question in that specific instance, not in general.

For "simple" data (e.g., the built-in types), it's also likely to be more
efficient to return by value than by reference. I'd personally be more
inclined to return by (const) reference something that had a significant
copy-construction overhead.

-Howard
 
J

JKop

I've read all your posts on this.

I would only use my const references method if there's an actual
variable/object within the class which I want to be read-only. In no other
place would I use it.


For example, a simple read-only instance counter:


#include <iostream>


class Human
{
private:

static unsigned long int prv_population_earth;

public:

static const unsigned long int& population_earth;

Human(void)
{
++prv_population_earth;
}

Human(Human&)
{
++prv_population_earth; //Cloning is a reality!
}

~Human(void)
{
--prv_population_earth;
}


};
unsigned long int Human::prv_population_earth = 0;
const unsigned long int& Human::population_earth =
Human::prv_population_earth;


int main(void)
{
std::cout << "Population of Earth: " << Human::population_earth;

//Human::population_earth += 5; //Compile error
}



Here's an obvious situation where the reference will always refer to a
proper variable/object which is a part of the class, whether it be static or
otherwise.


-JKop
 
D

DaKoadMunky

I don't understand your logic.
Let's say I change the int to a double .

A) MY CODE: Change the "const int&" to "const double&"

B) YOUR CODE: Change "int GetColour(void)" to "double GetColour(void)".

From my original post...

"So long as the data represented still conceptually represents an integer than
changes to the implementation need not have great impact."

With the above example you are doing more than changing the location and or
representation of your data. You are changing the meaning of "colour" for your
class.

Sometimes that may be necessary. If making "colour" an int was not the right
initial decision than you may very well have to change it to double when you
realize the original decision was wrong.

I would argue this goes beyond a mere implementation detail. How and where the
data is stored is an implementation detail. The type of the data as viewed by
users of the class, particularly for calling getters and setters, is part of
the abstraction/contract/interface/etc for the class.

Your example quoted here changes/violates that abstraction/contract etc...
To which code do you refer? My first one with member functions that return
const references, or my second one which has public const member references
which are initialized with private non-const variables?

All my thoughts were driven by your first solution.
Brian F. Seaberg
Naperville, Illinois
Delray Beach, Florida
 
J

Julie

Howard said:
But then you break everyone's code that uses your original object! They now
have to change their code to use your new object, whereas if you just
returned the computed value from the accessor, the user of your class never
even has to know how that value was computed, how it was stored, or
anything. They never even know the underlying accessor code was changed at
all. They get the value they want when they ask for it, and that's all they
should care about.

??? Explain to me how you _break_ existing code by creating a new or derived
class. In every scenario that I've been involved in, creating a new/derived
class broke nothing -- and those that wanted the extended/different behavior
provided by the new class, modified their code.

Maybe they _should_ know that the accessor has changed, perhaps in subtle ways
that the modifying author didn't foresee...
Experience has taught me this well. In writing database-access programs, I
often had to change something on the back end, and I was in general
*required* to not change the interface. When the user wants the trip
mileage, they never need to know if that was a stored value, or if it was
computed via a third-party mileage lookup. WIth thousands of users of our
system spread across the country, it was *very* important to make these kind
of changes as invisible to the users as possible. (It often took at least a
month to propogate changes to users when we had to make an interface change,
and we had to in effect maintain two system during such transitions...what a
pain!)

Sure, there are many (most!) cases where a functional-based accessor is the
appropriate implementation -- but I'm not limiting myself to saying that it is
the _only_ way to implement accessors.
Only in the internal behavior of the object, not in the interface or the
contract to provide the requested value.

That is correct, but you are presuming that the only imposed requirements are
for the interface and returned value. There are other characteristics that
must be taken into account when implementing a public function, and one of
those are performance characteristics.
I don't understand this. Why is a guarantee to "immediate and absolute"
access to the underlying data a requirement? What does the user of the
class care how the data is stored, represented, obtained or calculated? The
contract is to provide the value asked for, not (in general) a specification
of how that value is obtained or stored.

"Immediate and absolute" access is only a requirement if necessary, hardly an
absolute for all (most?) implementations.

In most cases, the user will not care how the data is stored, but they may care
how the data is accessed *or* computed.

Here is a simple example: suppose you are using a string class that implements
the length as a function (string::length()). Now, when iterating over that
string in a for-loop, they need to decide on:

size_t length = str.length();
for (int index=0; index<length; index++) // etc.

-- or --

for (int index=0; index<str.length(); index++) // etc.

If the implementation details of length() is unknown, they have to resort to
the first.

However, if the length is exposed as a public data reference, then it can be
guaranteed that str.length is immediate access and results in:

class string
{
public:
const size_t & length;
// etc.
};

for (int index=0; index<str.length; index++) // etc.

Finally, I'm not advocating this idiom in any particular case, just saying that
in the given situation, it may be appropriate and suitable. If you happen to
disagree, that is perfectly fine with me, I'm not trying to convince you
otherwise, I'm just leaving my coding options open.
 
R

Richard Herring

[QUOTE="Julie said:
What happens if you move to a more sophisticated colour model? Instead
of storing an int, maybe you need to compute something from the user's
choice, the available palette, the kind of display device etc...

No, you create a new or derived class.[/QUOTE]

Why? The interface hasn't changed, this is just an improved
implementation. The client code doesn't need to be changed at all to
make use of it. It might not even need to be recompiled.
You have now just changed the behavior of the original function with respect to
performance, which is a very real and tangible change.

The interface is unchanged. What do you mean by "performance" ? Can the
client code tell that anything has changed?
This is why I advocate const reference member accessors to simple data -- it
*guarantees* that access to the underlying data

*What* underlying data? The point here is that it might not even exist.
is immediate and absolute,

What is "unimmediate" or "unabsolute" about the value returned by a
function which returns by value?
and
will stay that way.

You mean that the designer is not at liberty to change something that's
part of the implementation, not the interface.
It doesn't break encapsulation as some have said,

It unnecessarily exposes implementation detail.
but
creates an immutable contract between the class designer and class user.

A contract that's unnecessarily restrictive of the designer.
You
can't get that w/ functions.

(Functions returning by value, I assume you mean.)

Indeed you don't, and a good thing too.
 
K

Karl Heinz Buchegger

JKop said:
I don't understand your logic.

Let's say I change the int to a double .

Don't change it from int to double.
Change it from:
the int is a member of your class
to
the int value is looked up in a database

The reference needs some int variable to
return a reference. Where is this variable
located in case of lookup in database?
 
K

Karl Heinz Buchegger

JKop said:
DaKoadMunky posted:


I don't understand your logic.

Let's say I change the int to a double .

A) MY CODE: Change the "const int&" to "const double&"

B) YOUR CODE: Change "int GetColour(void)" to "double GetColour(void)".

Karl mentioned something about this too, but I still don't see your
argument.

Another example:

Think of a class that handles circles.

class Circle
{
public:
Circle( double Radius ) : m_Radius( Radius ), m_Area ( PI * Radius * Radius ) {}

void SetRadius( double Radius ) { m_Radius = Radius; m_Area = PI * Radius * Radius; }

const double& Area() { return m_Radius; }

protected:
double m_Radius;
double m_Area;
};

The important thing is the member m_Area and how you return it in function Area().
I used your method.
The idea was: since calculating the area is a 'costly' operation and you expect
the rest of your program to use that area often, you compute the area as early
as possible and simply cache the result.

Now time goes by and you notice something stanga about your program: Area isn't
called nearly as often as you originally thought it would be. On the other hand
your program seems to take up a lot of memory and there are millions of Circle
objects around. So any way of conserving memory would be a good thing to do.
Well. Since function Area() isn't called that often, you could change your strategy:
instead of precomputing m_Area whenever you have a Radius and caching that, you
simply calculate the area when it is needed.
But you have 1 important thing: You are not allowed to change the function interface
in any way: all the function signatures have to stay the same!

Now your method of returning a reference turns into a bommerang. It is nearly
impossible to do that otherwise simple change of dropping the m_Area member
variable and mofing the calulation into function Area(). You simply don't have
a variable for returning a reference to it.

Now compare this to:

class Circle
{
public:
Circle( double Radius ) : m_Radius( Radius ), m_Area ( PI * Radius * Radius ) {}

void SetRadius( double Radius ) { m_Radius = Radius; m_Area = PI * Radius * Radius; }

double Area() { return m_Radius; }

protected:
double m_Radius;
double m_Area;
};

This is the original class with the Area() function as most of us would write
it. On any decent compiler it produces code which is no less efficient then
your return by reference version.

But now the modification is simple:

class Circle
{
public:
Circle( double Radius ) : m_Radius( Radius ) {}

void SetRadius( double Radius ) { m_Radius = Radius }

double Area() { return PI * Radius * Radius; }

protected:
double m_Radius;
};

All the function signatures stay the same. Only the internals of the
class have changed.

By returning a reference, you have exposed some information from
inside the class to the outside world: there is a double variable
or otherwise I would not be able to return a reference to it.
 
J

JKop

Karl Heinz Buchegger posted:
Don't change it from int to double.
Change it from:
the int is a member of your class
to
the int value is looked up in a database

The reference needs some int variable to
return a reference. Where is this variable
located in case of lookup in database?

Each block of cheese has a colour. Yellow, white, or maybe even green. This
is a fundamental attribute of a block of cheese.

This is all, and only all, what my code does. Firstly, here was the original
way of working with it:

int main()
{
Cheese block;

TakesInt(block.colour);

//I've no problem at all with the above

block.colour = 4;

//I don't like colour changing cheese
}

And then with my code:

int main()
{
Cheese block;

TakesInt(block.colour);

block.colour = 4; // Compile ERROR
}



That's all I was ever ever ever ever talking about, a read-only member
variable. No more, no less, just a read-only member variable. I reiterate, a
read-only member variable.


-JKop
 
K

Karl Heinz Buchegger

JKop said:
Karl Heinz Buchegger posted:


Each block of cheese has a colour. Yellow, white, or maybe even green. This
is a fundamental attribute of a block of cheese.

I don't doubt that. But who says that that colour needs to be *stored*
in the Cheese object itself.

What you are saying is, in terms of C++
Every Cheese object needs to have a member function which returns
the colour.
That's ok, and I agree with that. But with what I don't agree is *how*
this function comes up with the value it returns. The value doesn't
need to be stored in the Cheese object itself.
This is all, and only all, what my code does. Firstly, here was the original
way of working with it:

int main()
{
Cheese block;

TakesInt(block.colour);

//I've no problem at all with the above

block.colour = 4;

//I don't like colour changing cheese
}

And then with my code:

int main()
{
Cheese block;

TakesInt(block.colour);

block.colour = 4; // Compile ERROR
}

That's all I was ever ever ever ever talking about, a read-only member
variable. No more, no less, just a read-only member variable. I reiterate, a
read-only member variable.

No problem with that. Your intention is clear.
The rest of the group is talking about, why this is not such a good
idea. To be precise: To have a read-only member would be a good idea.
The problem is, that it can't be implemented in C++ the way most of
us would like it to be. You have shown an implementation and we have
talked about why we are not happy with it.
 
J

JKop

Karl Heinz Buchegger posted:
No problem with that. Your intention is clear.
The rest of the group is talking about, why this is not such a good
idea. To be precise: To have a read-only member would be a good idea.
The problem is, that it can't be implemented in C++ the way most of
us would like it to be. You have shown an implementation and we have
talked about why we are not happy with it.


Are you saying that a class should have no public member variables at all?
That's what it sounds like. In my example, there *is* a public member
variable, and it is read-only.


-JKOp
 
K

Karl Heinz Buchegger

JKop said:
Karl Heinz Buchegger posted:


Are you saying that a class should have no public member variables at all?

Yep.
Exposing public member variables always ties the code that uses your
class with the class internals. Thus you cannot change class internals
without breaking the code that uses it.
 
J

JKop

JKop posted:
#include <iostream>


class Human
{
private:

static unsigned long int prv_population_earth;

public:

static const unsigned long int& population_earth;

Human(void)
{
++prv_population_earth;
}

Human(Human&)
{
++prv_population_earth; //Cloning is a reality!
}

~Human(void)
{
--prv_population_earth;
}


};
unsigned long int Human::prv_population_earth = 0;
const unsigned long int& Human::population_earth =
Human::prv_population_earth;


int main(void)
{
std::cout << "Population of Earth: " << Human::population_earth;

//Human::population_earth += 5; //Compile error
}


Or alternatively, you can just declare it const and use const_cast in the
member functions. Come to think of it, that's why I'd prefer over using
const references!

-JKop
 
A

Andre Kostur

JKop said:
Or alternatively, you can just declare it const and use const_cast in
the member functions. Come to think of it, that's why I'd prefer over
using const references!

Um, no (the const_cast idea). If you declare it const, and then attempt to
modify it via a const_cast, you have entered the realm of Undefined
Behaviour. Theoretically I can dream up a computer where one could mark an
arbitrary range of memory addresses as read-only. On this computer, the
compiler is perfectly within it's rights to mark the memory where that
member is as read-only. Then when you try to write to that memory area,
the CPU traps and your program blows up. const_cast is for interfacing
with legacy code where the function declarations aren't const-correct, but
the actual code behaves as if the consts were there anyway.
 
H

Howard

Julie said:
??? Explain to me how you _break_ existing code by creating a new or derived
class. In every scenario that I've been involved in, creating a new/derived
class broke nothing -- and those that wanted the extended/different behavior
provided by the new class, modified their code.

Below, I did explain. As I said, my system was used by thousands across the
country, and if there was any back-end database change or access method
change, I was in general *required* to implement without changing the public
interfaces. This has nothing to do with providing some neat new toy they
can play with, but changing the way our classes do their work internally,
which is none of the user's business.

Sure, I had performance requirements to accomodate, but those drove how I
internally did my work. If it takes a month to propogate interface changes
out to the users, what good is that if it saves a few milliseconds of
functon call time? I had to make changes to the back end fairly frequently
(some of them required by the federal gov't), and there's no way we could
send out a whole round of software to the uers every time we changed how we
stored or accessed our data.
Maybe they _should_ know that the accessor has changed, perhaps in subtle ways
that the modifying author didn't foresee...


Sure, there are many (most!) cases where a functional-based accessor is the
appropriate implementation -- but I'm not limiting myself to saying that it is
the _only_ way to implement accessors.

Ok, but your statement that started me off responding here was "No, you
create a new or derived class." And I'm suggesting that's not the common
way to change your internal implementation. The whole point of
encapsulation is that the internal workings are private, and not the
business of anyone using the object (unless otherwise specified explicitly,
such as specific side-effects).
That is correct, but you are presuming that the only imposed requirements are
for the interface and returned value. There are other characteristics that
must be taken into account when implementing a public function, and one of
those are performance characteristics.

Sure, but that's a separate specification, not part of the contract provided
by the function interface. And in the case of something simple like
computing a color from some values, how much of a hit are we talking?
COmpare that to the month it took me to distribute code changes to the
users!
"Immediate and absolute" access is only a requirement if necessary, hardly an
absolute for all (most?) implementations.

In most cases, the user will not care how the data is stored, but they may care
how the data is accessed *or* computed.

Here is a simple example: suppose you are using a string class that implements
the length as a function (string::length()). Now, when iterating over that
string in a for-loop, they need to decide on:

size_t length = str.length();
for (int index=0; index<length; index++) // etc.

-- or --

for (int index=0; index<str.length(); index++) // etc.

If the implementation details of length() is unknown, they have to resort to
the first.

However, if the length is exposed as a public data reference, then it can be
guaranteed that str.length is immediate access and results in:

class string
{
public:
const size_t & length;
// etc.
};

for (int index=0; index<str.length; index++) // etc.

Profiling can tell you if performance is suffering, and some published
knowledge about internals can indeed tell you if you can do something
better. But if you're *depending* upon the internal implementation details
of a class you use in order to design how you use the class, then you most
definitely *are* breaking encapsulation. That's exactly what encapsulation
is about, keeping those internals private. (Look at most third-party
libraries...you get a .dll or .lib or some other binary of the actual
implementation code, and only header files as source code. They don't
*want* you to know their internal workings! It's probably copyrighted, for
that matter.)
Finally, I'm not advocating this idiom in any particular case, just saying that
in the given situation, it may be appropriate and suitable. If you happen to
disagree, that is perfectly fine with me, I'm not trying to convince you
otherwise, I'm just leaving my coding options open.

Ok, I can understand that. It's a "good thing" to look carefully at the
specific problem at hand before making decisions as to how to solve it.

But I still say it's better, in general, for accessors to return by value
than by reference. (And I also agree with others that, even in the specific
case given, it's better, because it's always possible that the object that
returned the reference could be destroyed while the calling code still
wanted to use that value.)

-Howard
 
J

JKop

Andre Kostur posted:
Um, no (the const_cast idea). If you declare it const, and then
attempt to modify it via a const_cast, you have entered the realm of
Undefined Behaviour. Theoretically I can dream up a computer where one
could mark an arbitrary range of memory addresses as read-only. On
this computer, the compiler is perfectly within it's rights to mark the
memory where that member is as read-only. Then when you try to write
to that memory area, the CPU traps and your program blows up.
const_cast is for interfacing with legacy code where the function
declarations aren't const-correct, but the actual code behaves as if
the consts were there anyway.


If some-one was brilliant enough to devise such a system, I believe that
they would be compentant enough to know of the well documented const_cast
operator. To ignore const_cast would be a fault.


-JKop
 
J

JKop

Howard posted:
But I still say it's better, in general, for accessors to return by
value than by reference. (And I also agree with others that, even in
the specific case given, it's better, because it's always possible that
the object that returned the reference could be destroyed while the
calling code still wanted to use that value.)

-Howard


Just as some-one with similar intelligence could:


SomeClass* p_some_class = new SomeClass;

//Time goes by

delete p_some_class;

//Time goes by

p_some_class->DoStuff();


-JKop
 
A

Andre Kostur

JKop said:
Andre Kostur posted:



If some-one was brilliant enough to devise such a system, I believe
that they would be compentant enough to know of the well documented
const_cast operator. To ignore const_cast would be a fault.

Not from the Standard point of view. The Standard states that attempting
to modify a const object via a const cast is undefined behaviour. End of
story.
 
J

JKop

Andre Kostur posted:
Not from the Standard point of view. The Standard states that
attempting to modify a const object via a const cast is undefined
behaviour. End of story.

What is the function, the purpose, of const_cast? It's essentially for
editing a const object. If this is undefined behaviour, they operator should
be renamed to:

const_cast_undefined_behaviour


-JKop
 
K

Karl Heinz Buchegger

JKop said:
Andre Kostur posted:


What is the function, the purpose, of const_cast?

As Andrea has already pointed out:

To allow for interfacing with legacy code, which, for whatever
reasons, is not const correct and you can't change that code.
 
O

Old Wolf

JKop said:
What is the function, the purpose, of const_cast? It's essentially for
editing a const object. If this is undefined behaviour, they operator should
be renamed to:

const_cast_undefined_behaviour

It's not undefined behaviour to use const_cast.
It's undefined behaviour to modify a constant object (whether
it be via a const_cast, or any other method).

There is no UB here:

int x;
int const &y = x;
const_cast<int &>(y) = 1;

(BTW this is another reason that your idea of having const reference
members instead of 'get functions' is bad: anyone can just const_cast
and modify the original, whereas with 'get functions' it's much
harder to modify the original.)
 

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
473,780
Messages
2,569,611
Members
45,276
Latest member
Sawatmakal

Latest Threads

Top