Read-only, as opposed to const member

J

JKop

Back when I read my first C++ book, I was given the following scenario:

class Cheese
{
public:

int number_of_holes;

int colour;

};


The thing is, you want the "user" of this class to be able to read from the
two above variables, but no be able to change them. The book retardly gave
the following:

class Cheese
{
private:

int number_of_holes;

int colour;

public:

int GetNumberOfHoles(void);

int GetColour(void);
};



What do yous think of the following:


class Cheese
{
private:

int number_of_holes;

int colour;

public:

const int& GetNumberOfHoles(void)
{
return number_of_holes;
}

const int& GetColour(void)
{
return colour;
}

};

int main(void)
{
Cheese chalk;

chalk.GetNumberOfHoles();
}


I wonder was so many books think they have to show you the stupid way before
they give you the lean, mean, efficient way.


-JKop
 
J

JKop

An even better idea!:


class Cheese
{
private:

int prv_number_of_holes;

int prv_colour;

public:

const int& number_of_holes;

const int& colour;

Cheese(void) : number_of_holes(prv_number_of_holes),
colour(prv_colour)
{
;
}

void Blah(void)
{
prv_number_of_holes = 52;

prv_colour = 2;
}

};


int main(void)
{
Cheese chalk;

TakesInt(chalk.number_of_holes);

chalk.number_of_holes = 52; //Compile error


}


This way, the actual class member functions and also friend functions have
write-access to these member variables, while the "user" only has read-only
access!


-JKop
 
R

Richard Herring

JKop <[email protected]> said:
Back when I read my first C++ book, I was given the following scenario:

class Cheese
{
public:

int number_of_holes;

int colour;

};


The thing is, you want the "user" of this class to be able to read from the
two above variables, but no be able to change them. The book retardly gave
the following:

class Cheese
{
private:

int number_of_holes;

int colour;

public:

int GetNumberOfHoles(void);

int GetColour(void);

Both of those should be const functions, and the "void" is unidiomatic.
};



What do yous think of the following:


class Cheese
{
private:

int number_of_holes;

int colour;

public:

const int& GetNumberOfHoles(void)
{
return number_of_holes;
}

const int& GetColour(void)
{
return colour;
}

I think those should be const functions, too, and you don't need the
"void".
};

int main(void)
{
Cheese chalk;

chalk.GetNumberOfHoles();

Pointless. Didn't you mean

something = chalk.GetNumberOfHoles();

? There's an important difference.
}


I wonder was so many books think they have to show you the stupid way before
they give you the lean, mean, efficient way.

Because it's the safe way? Once you start returning references, the
next thing is to try returning references to temporaries, with hilarious
consequences.

What do you think you would gain by returning a reference?

I doubt that returning an int by value is likely to be any more obese or
less efficient than returning a const reference. Even if you'd
substituted some class with a costly copy constructor, don't forget that
ultimately you want to _do_ something with the value of whatever the
function returns, or why call it in the first place? And that means
that somewhere or other there's likely to be a copy operation anyway,
whether you return a value or a reference. Given the existence of return
value optimisation, the difference in cost may be less than you
expected.
 
K

Karl Heinz Buchegger

JKop said:
What do yous think of the following:

class Cheese
{
private:

int number_of_holes;

int colour;

public:

const int& GetNumberOfHoles(void)
{
return number_of_holes;
}

const int& GetColour(void)
{
return colour;
}

};

int main(void)
{
Cheese chalk;

chalk.GetNumberOfHoles();
}

I wonder was so many books think they have to show you the stupid way before
they give you the lean, mean, efficient way.

On most typical machines this is no more efficient then returning
the values directly. There is literally no point in replacing
a pass by value with a pass per reference for builtin types
like int, double, char, etc...
 
K

Karl Heinz Buchegger

JKop said:
An even better idea!:


class Cheese
{
private:

int prv_number_of_holes;

int prv_colour;

public:

const int& number_of_holes;

const int& colour;

Cheese(void) : number_of_holes(prv_number_of_holes),
colour(prv_colour)
{
;
}

void Blah(void)
{
prv_number_of_holes = 52;

prv_colour = 2;
}

};


int main(void)
{
Cheese chalk;

TakesInt(chalk.number_of_holes);

chalk.number_of_holes = 52; //Compile error


}

This way, the actual class member functions and also friend functions have
write-access to these member variables, while the "user" only has read-only
access!

and when the internals of that class Cheese change, all the code that uses
that class breaks immediatly. Congratulations: you just found a clever way
of breaking the concept of encapsulation :)

Trust your compilers optimizer!
He may do more then you expect.
 
P

Peter van Merkerk

JKop said:
Back when I read my first C++ book, I was given the following scenario:

class Cheese
{
public:

int number_of_holes;

int colour;

};


The thing is, you want the "user" of this class to be able to read from the
two above variables, but no be able to change them. The book retardly gave
the following:

class Cheese
{
private:

int number_of_holes;

int colour;

public:

int GetNumberOfHoles(void);

int GetColour(void);
};



What do yous think of the following:


class Cheese
{
private:

int number_of_holes;

int colour;

public:

const int& GetNumberOfHoles(void)
{
return number_of_holes;
}

const int& GetColour(void)
{
return colour;
}

};

int main(void)
{
Cheese chalk;

chalk.GetNumberOfHoles();
}

I wonder was so many books think they have to show you the stupid way before
they give you the lean, mean, efficient way.

If those funtions are inlined and if you use an optimizing compiler it
will most likely not make a difference whether you return by reference
or return by value. In trivial cases like this example the contents of
the main() function would most likely be completely optimized away
(since everything is inline in this example the compiler can determine
that there are no observable side effects). In cases where the main()
function uses the return values (so the calls to the chalk object can
not be optimized away), the compiler will most likely generate exactly
the same code for both cases.

Note that the optimization story changes when the member functions of
the Cheese class are not inlined (i.e. when the implementation of these
member functions are in a different translation unit). In this case the
compiler does not know what happens inside the member functions, and
therefore the optimizer must be conservative and must call the functions.

For simple types (like int), that have zero construction and destruction
overhead and have very low copy overhead (about the same as copying
pointers which typically happens when dealing with references), there is
most likely no performance gain by using references. In fact in some
cases returning by reference instead of returning by value can even be
slower (taking a peek at assembly code generated by the compiler can be
very instructive). Of course if you have objects that are costly to
copy(construct) using references can give significant performance
benefits, and should be used whenever appropriate.

The reason why books start with the return by value example probably for
educational reasons; start simple first and discuss the more advanced
topics later. Also note that performance is only one of the lofty goals
to pursue. The net effect of the micro optimizations you are after is
usually quite small or even immeasurable. In my experience you are lucky
if you can improve performance that way by more than 20% (there are
exceptions but those are rare). On the other hand it is not rare for
high-level optimizations (e.g. better algorithms) to improve performance
by more than an order of magnitude. You have to consider carefully on
what you spend time. That being said there is obviously no point in
writing needlessly inefficient code.
 
T

tom_usenet

Back when I read my first C++ book, I was given the following scenario:

class Cheese
{
public:

int number_of_holes;

int colour;

};


The thing is, you want the "user" of this class to be able to read from the
two above variables, but no be able to change them. The book retardly gave
the following:

class Cheese
{
private:

int number_of_holes;

int colour;

public:

int GetNumberOfHoles(void);

int GetColour(void);
};

Yes, they miss the const qualification on the member functions, which
is a bad idea.
What do yous think of the following:


class Cheese
{
private:

int number_of_holes;

int colour;

public:

const int& GetNumberOfHoles(void)
{
return number_of_holes;
}

const int& GetColour(void)
{
return colour;
}

};

It also misses the const qualification. What's more, the return by
reference exposes the implementation of Cheese, since the class needs
int member variables to return references to. What if you decide to
store the colour as a class internally? You don't want to have to
change the interface.

In addition, returning built-ins by reference is usually a
pessimization - it is slower than return by value, at least until the
optimizer turns it back into return by value!
int main(void)
{
Cheese chalk;

chalk.GetNumberOfHoles();
}
I wonder was so many books think they have to show you the stupid way before
they give you the lean, mean, efficient way.

Neither way is good, but yours is actually more stupid.

Tom
 
J

JKop

Peter van Merkerk posted:
You have to consider carefully on what you spend time. That being said
there is obviously no point in writing needlessly inefficient code.


I have all the time in the world! Ahh... the joys of unemployment.


-JKop
 
J

JKop

Richard Herring posted:
Both of those should be const functions, and the "void" is unidiomatic.

int GetNumberOfHoles(void) const;

int GetColour(void) const;


"unidiomatic" is a subjective term.
I think those should be const functions, too, and you don't need the
"void".


const int& GetNumberOfHoles(void) const;

const int& GetColour(void) const;

I am fully aware that I don't need the "void".
Pointless. Didn't you mean

something = chalk.GetNumberOfHoles();


No.

I didn't suggest there was a point.

? There's an important difference.

Because it's the safe way? Once you start returning references, the
next thing is to try returning references to temporaries, with
hilarious consequences.


Hence the absence of the returning of references to temporaries.

What do you think you would gain by returning a reference?

I doubt that returning an int by value is likely to be any more obese
or less efficient than returning a const reference. Even if you'd
substituted some class with a costly copy constructor, don't forget
that ultimately you want to _do_ something with the value of whatever
the function returns, or why call it in the first place? And that
means that somewhere or other there's likely to be a copy operation
anyway, whether you return a value or a reference. Given the existence
of return value optimisation, the difference in cost may be less than
you expected.


If I'm pedantic about it, there's greater probability of the reference form
being more efficent than the return-by-value form, as the return-by-value
form, under the duress of the Standard, *may* create a temporary.


-JKop
 
J

JKop

Karl Heinz Buchegger posted:
and when the internals of that class Cheese change, all the code that
uses that class breaks immediatly. Congratulations: you just found a
clever way of breaking the concept of encapsulation :)

Trust your compilers optimizer!
He may do more then you expect.


So long as you have read-only variables/objects, it should be grand.


-JKop
 
R

Richard Herring

Both of those should be const functions, and the "void" is unidiomatic.

int GetNumberOfHoles(void) const;

int GetColour(void) const;

"unidiomatic" is a subjective term.[/QUOTE]

Of course. That's why I didn't say "wrong". Nevertheless, it's more to
type and marks you as possibly being an unregenerate C programmer.
const int& GetNumberOfHoles(void) const;

const int& GetColour(void) const;

I am fully aware that I don't need the "void".

Good. Save yourself a few keystrokes.
No.

I didn't suggest there was a point.

I thought you were trying to make one? If you discard the results of the
side-effect-free function call, the whole thing could be as-if'd away to
nothing.
Hence the absence of the returning of references to temporaries.

Well, good for you.
If I'm pedantic about it, there's greater probability of the reference form
being more efficent than the return-by-value form, as the return-by-value
form, under the duress of the Standard, *may* create a temporary.

And it may not (strange meaning of "duress" you have there.) But getting
a value from the reference involves an extra indirection behind the
scenes, so there's a definite inefficiency there.
 
D

DaKoadMunky

For accessors I prefer the return-by-value approach rather than the
return-by-reference approach.

The return-by-reference approach I believe creates an implementation
dependency.

In your case you are returning references to integers. This seems to work out
fine because you have chosen to store integers and thus have integers with
which to initialize your references.

What happens though if you decide you want to modify your class to change how
and/or where your data is stored?

Where then is the integer to initialize your reference going to come from?

If you no longer have an integer with which to initialize a reference are you
going to change the return type of your accessor? Clients might not
appreciate that.

I think returning by value alleviates some of these problems. The underlying
storage mechanism can change at will. So long as the data represented still
conceptually represents an integer than changes to the implementation need not
have great impact.

As an aside, I also prefer the return-by-value for safety reasons. Your
accessor returns a const reference which of course communicates to its caller
that it may not modify the referenced data. That constness though is easily
cast away which can result in modifications that bypass any "set" functions
that might be in place. Returning by value seems to also alleviate the
possibility that your object could somehow be corrupted.

Of course some people say you shouldn't write code that guards against
deliberate misuse...that writers of such code deserve whatever fate befalls
them.
 
J

JKop

DaKoadMunky posted:
For accessors I prefer the return-by-value approach rather than the
return-by-reference approach.

The return-by-reference approach I believe creates an implementation
dependency.

In your case you are returning references to integers. This seems to
work out fine because you have chosen to store integers and thus have
integers with which to initialize your references.

What happens though if you decide you want to modify your class to
change how and/or where your data is stored?

Where then is the integer to initialize your reference going to come
from?

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.

If you no longer have an integer with which to initialize a reference
are you going to change the return type of your accessor? Clients
might not appreciate that.

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?
As an aside, I also prefer the return-by-value for safety reasons.
Your accessor returns a const reference which of course communicates to
its caller that it may not modify the referenced data. That constness
though is easily cast away which can result in modifications that
bypass any "set" functions that might be in place. Returning by value
seems to also alleviate the possibility that your object could somehow
be corrupted.

This could lead into the realms of whether there should be const in C++ at
all. There's no const in Python, and their official reason is "We're all
consenting adults here!".


-JKop
 
J

JKop

Karl Heinz Buchegger posted:
and when the internals of that class Cheese change, all the code that
uses that class breaks immediatly. Congratulations: you just found a
clever way of breaking the concept of encapsulation :)

I really don't understand this at all. Could you please be more specific?

-JKop
 
R

Richard Herring

For accessors I prefer the return-by-value approach rather than the
return-by-reference approach.

The return-by-reference approach I believe creates an implementation
dependency.

In your case you are returning references to integers. This seems to
work out fine because you have chosen to store integers and thus have
integers with which to initialize your references.

What happens though if you decide you want to modify your class to
change how and/or where your data is stored?

Where then is the integer to initialize your reference going to come
from?

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)".[/QUOTE]

No, that's not what he's saying.

At the moment you have

int & GetColour() const { return colour; }
- or -
int GetColour() const { return colour; }

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...

OK, add some functionality to compute it:

private:
int ComputeComplicatedColour(int userColour, palette p, display d)
const;

If returning a value, no problem; just change your access function to
this:
int GetColour() const
{ return ComputeComplicatedColour(colour, palette, device); }

But if you're returning a reference:
int & GetColour() const
{ return reference to what??? }
 
E

E. Robert Tisdale

JKop said:
Back when I read my first C++ book, I was given the following scenario:

class Cheese {
public:
int number_of_holes;
int colour;
};

The thing is,
you want the "user" of this class to be able to read from the
two above variables, but no be able to change them.
The book retardly gave the following:

class Cheese {
private:
int number_of_holes;
int colour;
public:
int GetNumberOfHoles(void);
int GetColour(void);
};

What do yous think of the following:

class Cheese {
private:
int number_of_holes;
int colour;
public:
const int& GetNumberOfHoles(void) {
return number_of_holes;
}
const int& GetColour(void) {
return colour;
}

Yes, this is the preferred method
when returning a reference to a large object
instead of a small object like an int.

Actually, I might have written

const int& holes(void) const {
return number_of_holes;
}
const int& color(void) const {
return colour;
}

if your intent is *not* to change any part of the object.
};

int main(void) {
Cheese chalk;
chalk.GetNumberOfHoles();
// . . .
return 0;
}
I wonder was so many books think they have to show you the stupid way
before they give you the lean, mean, efficient way.

Is it stupid?

int main(int argc, char* argv[]) {
Cheese* pCheese = new Cheese;
const int& holes = pCheese->holes()
delete pCheese;
// holes is no longer a valid reference
std::cout << holes << " = holes" << srd::endl;
// undefined behavior
return 0;
}
 
J

JKop

E. Robert Tisdale posted:
// . . .
return 0;
}

The "return 0;" is a matter of preference. I don't put it in because I don't
give a hoot about my program returning anything.

I wonder was so many books think they have to show you the stupid way
before they give you the lean, mean, efficient way.

Is it stupid?

int main(int argc, char* argv[]) {
Cheese* pCheese = new Cheese;
const int& holes = pCheese->holes()
delete pCheese;
// holes is no longer a valid reference
std::cout << holes << " = holes" << srd::endl;
// undefined behavior


Which is exactly the same as doing something as equally as stupid as:

pCheese->MakeEaterSick();


I notice how you didn't complain about that.

---


Anyway, I entirely retract my original code. I've replaced it with the
supplementary code I posted.


-JKop
 
J

JKop

JKop posted:
E. Robert Tisdale posted:
int main(int argc, char* argv[]) {
Cheese* pCheese = new Cheese;
const int& holes = pCheese->holes()
delete pCheese;
// holes is no longer a valid reference
std::cout << holes << " = holes" << srd::endl; // undefined
behavior


I think I missed your point.

Are you referring to how one can bind a reference to the return value of a
return-by-value function, and then have not worry about the variable/object
ever going out of scope. Whereas with the above code, even though the
"holes" reference in main is still in scope, the variable to which it refers
is OUT of scope.

Good point. But then again, it's clearly evident that my code returns a
reference, as opposed to returning by value.

-JKop
 
J

Julie

Richard 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.
OK, add some functionality to compute it:

private:
int ComputeComplicatedColour(int userColour, palette p, display d)
const;

If returning a value, no problem; just change your access function to
this:
int GetColour() const
{ return ComputeComplicatedColour(colour, palette, device); }

You have now just changed the behavior of the original function with respect to
performance, which is a very real and tangible change.

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.
 
J

John Harrison

JKop said:
Karl Heinz Buchegger posted:


I really don't understand this at all. Could you please be more specific?

-JKop

Consider, boss says to you 'I no longer want you to store the colour in the
Cheese object. We have too many cheese objects and they're are taking up too
much memory. People aren't interested in the colour of cheese much, most of
the time its yellow anyway.'

'So what were are going to do instead is store the cheese colour in a
database and you can access the database when someone calls GetColour. Just
to make things really easy for you the database department have written this
handy API for you do use.'

void GetCheeseColourFromDatabase(Cheese* cheese_ptr, int* colour_ptr);

Now what do you do?

// returns reference to local variable
const int& Cheese::GetColour() const
{
int colour;
GetCheeseColourFromDatabase(this, &colour);
return colour;
}

// don't try this on more than one cheese
const int& Cheese::GetColour() const
{
static int colour;
GetCheeseColourFromDatabase(this, &colour);
return colour;
}

// leaks memory
const int& Cheese::GetColour() const
{
int* colour = new int;
GetCheeseColourFromDatabase(this, colour);
return *colour;
}

Essentially by returning a reference you have committed yourself to storing
the cheese colour as an int in your object somewhere. Sometimes its good not
to make that commitment.

john
 

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

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top