Jump to content

  • Log In with Google      Sign In   
  • Create Account


What are constant references used for?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
20 replies to this topic

#1 gasto   Members   -  Reputation: 261

Like
0Likes
Like

Posted 28 May 2014 - 03:48 PM

In C++, is there an actual usage for constant references, or is it there for completeness(and perhaps to add ambiguity.)?

I would simply use constant variable or a preprocessor substitution to a literal value.

Edited by gasto, 28 May 2014 - 04:46 PM.

Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.

Sponsor:

#2 phil_t   Crossbones+   -  Reputation: 3222

Like
9Likes
Like

Posted 28 May 2014 - 04:17 PM

Are you talking about c++?

 

A common usage is for function parameters when you want to avoid the overhead of copying the object, but still don't want the function to be able to modify the original.



#3 Ravyne   Crossbones+   -  Reputation: 6884

Like
7Likes
Like

Posted 28 May 2014 - 04:23 PM

A const reference is exactly what it says it is: the reference itself is always const and must be initialized to refer to a valid object, const means that the object can't be modified through the reference.

 

This is why its advisable to take parameters passing large objects that you don't intend to modify as a const reference -- even if the object itself is not const, passing by const reference ensures that the function can't modify the object. It helps enforce and documents the intended contract of the function. This principle need not be used in conjunction with function parameters though -- a const reference is useful any time you want to enforce immutability of a (potentially) mutable object, it just does so with a different name -- that of the reference.

 

A const reference can reference both const and non-const objects, but you can't take a non-const reference to a const object, so there's no backdoor to modifying a const object. At least not without further subversion of the type system.


Edited by Ravyne, 28 May 2014 - 04:26 PM.


#4 ApochPiQ   Moderators   -  Reputation: 14621

Like
7Likes
Like

Posted 28 May 2014 - 04:31 PM

Read-only visibility into an object is also a common use case, i.e. returning a const reference from a function.



#5 gasto   Members   -  Reputation: 261

Like
0Likes
Like

Posted 28 May 2014 - 05:05 PM

A const reference can reference both const and non-const objects, but you can't take a non-const reference to a const object, so there's no backdoor to modifying a const object. At least not without further subversion of the type system.


It would be difficult to imagine modifying a literal like 1 or the address of the first character of "Hello". In C that is undefined behaviour if allowed(if I recall correctly).
Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.

#6 gasto   Members   -  Reputation: 261

Like
0Likes
Like

Posted 28 May 2014 - 05:17 PM

Read-only visibility into an object is also a common use case, i.e. returning a const reference from a function.


Would it refer to anything? I mean, after the execution of the function, local variables with automatic block duration would be wiped out of memory, hence the reference returned by such hypothetical function would refer to garbage.
Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.

#7 Andy Gainey   Members   -  Reputation: 1977

Like
4Likes
Like

Posted 28 May 2014 - 05:20 PM

Literals aren't really a relevant consideration.  Think of references as pointers to some location in memory that holds some value.  That's essentially what they are; syntactic sugar on top of a pointer, with a few semantic bonuses to help the compiler understand what you're attempting to do and thereby do it efficiently, or tell you it can't be done and give a reasonable explanation as to why.

 

So if there's a chunk of memory that has a value, and you have a reference to that memory, then it wouldn't be that hard to imagine modifying that value.  Marking the reference as const just helps to protect you from your own forgetfulness, so that you don't accidentally modify something that other pieces of code are assuming won't be modified naively.

 

Note that if you ever happen to pass a literal value to a function that has a const reference parameter, the literal itself isn't what the reference refers to.  Instead, the literal gets copied/pushed onto the call stack as a function parameter, and thus that parameter is stored in memory and has an address.  (There might be optimizations whereby the literal is instead copied directly into a register and never stored to memory, but this probably only ever happens if the compiler knows for certain that you never use the reference in a way that would depend on the parameter being in memory.  In which case, the fact that the parameter is a const reference is in effect ignored by the compiler, since it didn't matter.)



"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas only, and not for things themselves." - John Locke

#8 Rattrap   Members   -  Reputation: 1547

Like
3Likes
Like

Posted 28 May 2014 - 05:40 PM

Would it refer to anything? I mean, after the execution of the function, local variables with automatic block duration would be wiped out of memory, hence the reference returned by such hypothetical function would refer to garbage.

Local variables, yes. But member variables of a class that hasn't gone out of scope would persist.

Look at std::vector, the const versions of the [] operator or the at function return a const reference to the requested index.

#9 gasto   Members   -  Reputation: 261

Like
-1Likes
Like

Posted 28 May 2014 - 07:20 PM

but this probably only ever happens if the compiler knows for certain that you never use the reference in a way that would depend on the parameter being in memory.


How wouldn't it? I mean, 5 is always going to be 5 and it is known at compile time.
Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.

#10 Servant of the Lord   Crossbones+   -  Reputation: 17961

Like
4Likes
Like

Posted 28 May 2014 - 08:24 PM

5 and "hello" are literals.

int and std::string are variables.

int& and std::string& are reference variables.

 

It's mostly pointless to pass an int by const reference because it is so small, but it is very useful to pass a std::string by const reference. Passing a std::string by value means you have to copy it. Passing a std::vector<std::string> by value means every element in the vector will be copied.

 

It is very useful to be able to say, "I don't want to copy this variable, but I want to pass read-only access to it." - this means either a const reference or a const pointer. It serves a really useful purpose.

 

Example:

ByteBuffer lotsOfData = LoadFile("myfile.data");
 
DoSomethingWithTheData(lotsOfData);

I'd much rather 'DoSomethingWithTheData()' takes the buffer by const reference than copying all the bytes.

 

[Edit:] Ah, perhaps you are meaning Type & const, instead of const Type & ? Yeah, that's pointless (and I think invalid code). But const Type & is definitely valuable.


Edited by Servant of the Lord, 28 May 2014 - 09:15 PM.

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.

[Fly with me on Twitter] [Google+] [My broken website]

All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.                                                                                                                                                            [Need web hosting? I personally like A Small Orange]
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal


#11 Ravyne   Crossbones+   -  Reputation: 6884

Like
0Likes
Like

Posted 28 May 2014 - 09:09 PM

 

A const reference can reference both const and non-const objects, but you can't take a non-const reference to a const object, so there's no backdoor to modifying a const object. At least not without further subversion of the type system.


It would be difficult to imagine modifying a literal like 1 or the address of the first character of "Hello". In C that is undefined behaviour if allowed(if I recall correctly).

 

 

You can't without subverting the type system -- and that's the point. But when you alias an object by using a different name -- say, with a pointer or reference -- the constness of that new name is carried from the new name, not the object. That's why the compiler enforces it this way -- the new name can be more restrictive (a const reference or const pointer can alias either a const or non-const object), but never less restrictive (which is why a non-const reference or non-const pointer can't alias a const object). You can always further subvert the type system until you have a non-const alias to a const object, and when you do the compiler will happily accept it and your program will not-so-happily crash when you attempt to modify it (typically. I don't know whether its undefined or unspecified, specifically, but its not legal code). 

 

Diving a little deeper on a related issue, its something of a misnomer in C or C++ to say that you have a "const int" -- the platform might (or might not) actually protect the memory that stores that int with some mechanism, or it might place it in the .TEXT segment rather than .DATA, but the languages don't require that. It would be more accurate to think of it as having an "int-sized memory location whose name carries and enforces the qualities of a const value". The compiler will do its best to keep constness transitory when you alias the original name or address with a new name, but this is C and C++ where the programmer is god -- there are always ways around.



#12 L. Spiro   Crossbones+   -  Reputation: 12686

Like
6Likes
Like

Posted 28 May 2014 - 09:12 PM

const TYPE & VAR is extremely useful, but given your responses to the answers you’ve gotten you still don’t seem to have a grasp on what they are and how they are useful.
 
Here are actual examples.
 
#1: Objects can be expensive to pass as copies so when possible you want to pass these types by reference or pointer.  Since you should only pass pointers when a NULL value is valid, you typically pass references.  In order to avoid letting a function/method modify the actual object via the reference, the reference should be const.

void CTexture2d::CreateFromImage( const CImage &_iImage /* Copying a CImage requires allocations, copying of large buffers, and deallocations on out-of-scope; pass as a reference. */ );
 
#2: Return a reference to a class member instead of a copy, if the copy incurs significant overhead, as with CImage.
const CImage & CTexture2d::SourceImage() const { return m_iImage; /* m_iImage is a member of CTexture2d here. */ }
 
#3: To indicate the difference between access on read and access on write. In the following example, we have an orientation class with a position (type CVector3). In order to avoid updating the matrix every time it is read, we want to set a dirty flag only when it is written.
 
enum {
    ORIENTATION_POS_DIRTY = (1 << 0),
    ORIENTATION_SCALE_DIRTY = (1 << 1),
    ORIENTATION_ROT_DIRTY = (1 << 2),
    ORIENTATION_NORMALIZE = (1 << 3),
};
// Read version.
const CVector & COrientation::Pos() const {
    // No dirty flags; just return the value for read-only.
    return m_vPos;
}
// Write version.
CVector & COrientation::Pos() {
    m_ui32Dirty |= ORIENTATION_POS_DIRTY;  // Setting this bit indicates that later we need to re-update our matrix.
    return m_vPos;
}
m_aChar.Pos() += m_aPlatform.Pos();  // Only character’s position vector was accessed for write; it will cause a matrix rebuild for the character, not for the platform. 
 
#4: It makes things easier and faster.
Bad:
		for ( u32 I = ui32Colors; I--; ) {
			bMatchPoints[1][I].fValues[0] = bMatchPoints[0][ui32Colors-1-I].fValues[0];	// Swap red.
			bMatchPoints[2][I].fValues[1] = bMatchPoints[0][ui32Colors-1-I].fValues[1];	// Swap blue.
			bMatchPoints[3][I].fValues[2] = bMatchPoints[0][ui32Colors-1-I].fValues[2];	// Swap green.
		}
Good:
		for ( u32 I = ui32Colors; I--; ) {
			const LSI_BLOCK & bSrc = bMatchPoints[0][ui32Colors-1-I];
			bMatchPoints[1][I].fValues[0] = bSrc.fValues[0];	// Swap red.
			bMatchPoints[2][I].fValues[1] = bSrc.fValues[1];	// Swap blue.
			bMatchPoints[3][I].fValues[2] = bSrc.fValues[2];	// Swap green.
		}
 

L. Spiro

Edited by L. Spiro, 28 May 2014 - 09:21 PM.

It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#13 osmanb   Crossbones+   -  Reputation: 1495

Like
4Likes
Like

Posted 29 May 2014 - 07:24 AM

I think the confusion is that the OP is confusing 'const' with literal. const does not mean a compile time constant. There are certain places where that is the case, but in the general case, it's just a type modifier that says that a particular variable can't be modified (without subverting the type system, etc...).

 

In a function: 'const char*' doesn't need to point to a compile-time string that's embedded in the executable. It can point to anything, anywhere. (It can even be re-assigned to point to other strings. Those could be variables you allocated on the stack, or dynamically, or they could be literals. The only thing that const means in that sense is that you can't use that pointer to modify the thing it's pointing it. That's it. Period.

 

Now re-read that paragraph and substitute reference for pointer. (With the exception that you can't re-assign the reference to point to something new.) It all applies. You can still construct a const reference to anything you want, regardless of how it was created/allocated. You just can't use that reference to modify the value.



#14 gasto   Members   -  Reputation: 261

Like
0Likes
Like

Posted 29 May 2014 - 11:37 AM

const TYPE & VAR
#3: To indicate the difference between access on read and access on write. In the following example, we have an orientation class with a position (type CVector3). In order to avoid updating the matrix every time it is read, we want to set a dirty flag only when it is written.
 

enum {
    ORIENTATION_POS_DIRTY = (1 << 0),
    ORIENTATION_SCALE_DIRTY = (1 << 1),
    ORIENTATION_ROT_DIRTY = (1 << 2),
    ORIENTATION_NORMALIZE = (1 << 3),
};
// Read version.
const CVector & COrientation::Pos() const {
    // No dirty flags; just return the value for read-only.
    return m_vPos;
}
// Write version.
CVector & COrientation::Pos() {
    m_ui32Dirty |= ORIENTATION_POS_DIRTY;  // Setting this bit indicates that later we need to re-update our matrix.
    return m_vPos;
}
m_aChar.Pos() += m_aPlatform.Pos();  // Only character’s position vector was accessed for write; it will cause a matrix rebuild for the character, not for the platform. 


How will the compiler discern between one overloaded function and the other? Both have the same signature.
Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.

#15 gasto   Members   -  Reputation: 261

Like
0Likes
Like

Posted 29 May 2014 - 11:40 AM

[Edit:] Ah, perhaps you are meaning Type & const, instead of const Type & ? Yeah, that's pointless (and I think invalid code). But const Type & is definitely valuable.


That is true. However, sneak-peeking on other's people interpretation is useful.
Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.

#16 Brother Bob   Moderators   -  Reputation: 7864

Like
4Likes
Like

Posted 29 May 2014 - 11:56 AM

How will the compiler discern between one overloaded function and the other? Both have the same signature.

The non-const overload is chosen for non-const objects/references, and the const overload is chosen for const objects/references.

#17 Rattrap   Members   -  Reputation: 1547

Like
0Likes
Like

Posted 29 May 2014 - 12:06 PM

How will the compiler discern between one overloaded function and the other? Both have the same signature.


const CVector & COrientation::Pos() const {
CVector & COrientation::Pos() {
The difference in the signature is the const at the end of the line. As Brother Bob mentioned, if the variable is declared const, the version with the const at the end will be called.

#18 L. Spiro   Crossbones+   -  Reputation: 12686

Like
0Likes
Like

Posted 29 May 2014 - 03:51 PM

Waste of a post and my time.
See below for why.


L. Spiro

Edited by L. Spiro, 29 May 2014 - 04:59 PM.

It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#19 Brother Bob   Moderators   -  Reputation: 7864

Like
3Likes
Like

Posted 29 May 2014 - 04:08 PM

 

How will the compiler discern between one overloaded function and the other? Both have the same signature.

In addition to the explanation given by Brother Bob and reiterated by Rattrap, I showed a different example:
m_aChar.Pos() += m_aPlatform.Pos();
Neither m_aChar nor m_aPlatform are const objects in this context (neither were originally declared with const).

Instead, one is being accessed for read and the other for write.
When possible, the compiler will always choose the const method if it exists.

If only the const method existed, the line would be invalid because you couldn’t write to m_aChar.
If only the non-const method existed, both m_aChar.Pos() and m_aPlatform.Pos() would go through the non-const version (which would set a dirty flag on m_aPlatform and needlessly cause an update to its matrix).

Since both exist, the compiler will try to use const for both m_aChar and m_aPlatform, but it will realize that m_aChar is being accessed for write, so it instead must use the non-const version. m_aPlatform is accessed for read so the default (the const version) remains as the compiler’s choice.


L. Spiro

It doesn't matter whether you write to the returned value or just read from it; the function that is called (the const or non-const vatiant) is determined exclusively based on the object it is called on. The non-const is called when possible, and the const is called when necessary.



#20 gasto   Members   -  Reputation: 261

Like
0Likes
Like

Posted 29 May 2014 - 05:36 PM

How will the compiler discern between one overloaded function and the other? Both have the same signature.

In addition to the explanation given by Brother Bob and reiterated by Rattrap, I showed a different example:
m_aChar.Pos() += m_aPlatform.Pos();
Neither m_aChar nor m_aPlatform are const objects in this context (neither were originally declared with const).

Instead, one is being accessed for read and the other for write.
When possible, the compiler will always choose the const method if it exists.

If only the const method existed, the line would be invalid because you couldn’t write to m_aChar.
If only the non-const method existed, both m_aChar.Pos() and m_aPlatform.Pos() would go through the non-const version (which would set a dirty flag on m_aPlatform and needlessly cause an update to its matrix).

Since both exist, the compiler will try to use const for both m_aChar and m_aPlatform, but it will realize that m_aChar is being accessed for write, so it instead must use the non-const version. m_aPlatform is accessed for read so the default (the const version) remains as the compiler’s choice.


L. Spiro

It doesn't matter whether you write to the returned value or just read from it; the function that is called (the const or non-const vatiant) is determined exclusively based on the object it is called on. The non-const is called when possible, and the const is called when necessary.


My head is spinning. Is this a C++ standard feature? From what I recall, a function's signature only includes its name and parameters. Is a reference return type considered a parameter?

Edited by gasto, 29 May 2014 - 05:37 PM.

Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS