Thread: confused by "const"

  1. #1
    Registered User
    Join Date
    Oct 2007
    Posts
    13

    confused by "const"

    in Lippman's essential c++, he wrote:

    In the following class, the val() member function does not directly modify the _val data member, but it does return a non-const reference to _val. Can val() still be declared as const?

    Code:
    class val_class { 
    public: 
       val_class( const BigClass &v ) 
       : _val( v ){} 
     
       // is this ok? 
       BigClass& val() const { return _val; } 
     
    private: 
       BigClass _val; 
    };
    No. Returning a non-const reference to _val in effect opens it to modification in the general program.Because a member function can be overloaded based on its constness, one solution to this problem is to provide two definitions: a const and a non-const version, such as the following:
    Code:
     
    class val_class { 
    public: 
       const BigClass& val() const { return _val; } 
       BigClass& val(){ return _val; } 
     // ... 
    };
    what I don't understand :
    1) what does he mean by saying "val() returns a non-const reference to _val"?so val() indeed returns a reference? how does that work?

    2)
    Code:
    val_class( const BigClass &v ) 
       : _val( v ){ }
    is this a class constructor which using member initialization list to provide a way to initialize a val_class object? or it actually makes v also a val_class object?

    3) in the second piece of code, should it be
    Code:
    class val_class { 
    public: 
       //are they necessary?
       val_class( const BigClass &v ) 
       : _val( v ){ }
       val_class( BigClass &v) : _val(v)  { }  
       
       const BigClass& val() const { return _val; } 
       BigClass& val(){ return _val; } 
    }
    uhhhhhhhhhh, so confused...............
    Last edited by laserlight; 10-18-2007 at 11:41 AM. Reason: Fixed the code tags: they end with /code, not \code

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    1) what does he mean by saying "val() returns a non-const reference to _val"?so val() indeed returns a reference? how does that work?
    A reference is basically an alias, so the caller can have a variable that is an alias to the object's member variable. For example:
    Code:
    #include <iostream>
    
    class X {
    public:
        X() : _val(0) {}
        int& val() { return _val; }
    private:
        int _val;
    };
    
    int main() {
        X x;
        std::cout << x.val() << std::endl;
        int& v = x.val(); // v is an alias for x._val
        v = 1; // we change v, but this changes x._val
        std::cout << x.val() << std::endl;
    }
    is this a class constructor which using member initialization list to provide a way to initialize a val_class object? or it actually makes v also a val_class object?
    Yes, it initialises _val to v. v is a BigClass const reference, and that is not changed by the use of an initialisation list.

    in the second piece of code, should it be
    No. The idea is that if you have a const val_class object, you should not be able to logically modify its state. As such, you should only be able to call const member functions. But if a const member function returns a reference, the caller has a way of getting an alias to a member variable, and thus the caller can change the state of the object, even though it is supposed to be const!

    A solution is to provide a pair of member functions. One is a const member function, and thus it returns a const reference to ensure that the state cannot be changed. The other is non-const, so it can just return a reference knowing full well that changing its state is to be expected.

    Now, you have two constructors. One takes a BigClass object by const reference, the other takes a BigClass object by reference. However, you do not intend to modify the BigClass object passed. You only want to initialise your new object using this BigClass object. Consequently, the constructor that takes the BigClass object by reference should be discarded.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Oct 2007
    Posts
    13
    Quote Originally Posted by laserlight View Post
    A reference is basically an alias, so the caller can have a variable that is an alias to the object's member variable. For example:
    Code:
    #include <iostream>
    
    class X {
    public:
        X() : _val(0) {}
        int& val() { return _val; }
    private:
        int _val;
    };
    
    int main() {
        X x;
        std::cout << x.val() << std::endl;
        int& v = x.val(); // v is an alias for x._val
        v = 1; // we change v, but this changes x._val
        std::cout << x.val() << std::endl;
    }

    Yes, it initialises _val to v. v is a BigClass const reference, and that is not changed by the use of an initialisation list.


    No. The idea is that if you have a const val_class object, you should not be able to logically modify its state. As such, you should only be able to call const member functions. But if a const member function returns a reference, the caller has a way of getting an alias to a member variable, and thus the caller can change the state of the object, even though it is supposed to be const!

    A solution is to provide a pair of member functions. One is a const member function, and thus it returns a const reference to ensure that the state cannot be changed. The other is non-const, so it can just return a reference knowing full well that changing its state is to be expected.

    Now, you have two constructors. One takes a BigClass object by const reference, the other takes a BigClass object by reference. However, you do not intend to modify the BigClass object passed. You only want to initialise your new object using this BigClass object. Consequently, the constructor that takes the BigClass object by reference should be discarded.
    Thanks, your answer cleared up most of my confusion. However, if class val_class provides a member function which returns a non-const reference that could be changed in consequent operations, and because they are all connected by reference, so changes done to the return reference of val() will also change _val, then change the original BigClass object v. then what's the point of passing the BigClass object v as a const reference at the beginning?

    Another question is, even v is passed as a const reference, but it still coud be changed in the way described above. Is it because those operations which change it are not done by calling a const member function of val_class but instead by doing explicit changes on the non-const reference returned by a member function?

    if so, can I draw conclusion as following?
    1) format like
    ReturnType MemberFunction const { }
    calling these member functions won't change the state of the class object (i.e. no change is done to private members )

    2) format like
    const Returntype & MemberFunction const {}
    means the returning value is a reference and it's a const so it can not be changed even in the function which calls those member functions

    3)format like
    Returntype & MemberFunction const{ }
    is wrong...


    I know my description is a whole mess...forgive my poor english.....

  4. #4
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    Code:
    val_class( const BigClass &v ) 
       : _val( v ){ }
    In that code, v is copied into _val. _val itself is not a reference, so when it is initialized with v it calls a copy constructor to copy v into its own data. Once that is done there is no relationship between _val and v, so changes to _val don't affect v (or the BigClass object passed in as v).

    1), 2), and 3) are correct, although there is a small exception to 1) when you use the mutable keyword that you don't have to worry about just yet.

  5. #5
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    then what's the point of passing the BigClass object v as a const reference at the beginning?
    To initialise the val_class object. Remember, the BigClass object that was passed in the constructor has nothing to do with the BigClass member variable of the val_class object other than that this member variable started life as a copy of v. Changes to this member variable do not affect v, and this is emphasized by the fact that v is passed by const reference.

    Another question is, even v is passed as a const reference, but it still coud be changed in the way described above.
    No, it cannot be modified within the constructor since it is passed by const reference.

    1) format like
    ReturnType MemberFunction const { }
    calling these member functions won't change the state of the class object (i.e. no change is done to private members )
    Yes. Strictly speaking the state of the object may be changed internally if the member variable is declared mutable, but this change of state should not be observable.

    2) format like
    const Returntype & MemberFunction const {}
    means the returning value is a reference and it's a const so it can not be changed even in the function which calls those member functions
    Yes.

    3)format like
    Returntype & MemberFunction const{ }
    is wrong...
    Not necessarily. It would be wrong if it returns a reference to a member variable. Take for example a print() method that basically works like the overloaded operator<< for ostreams:
    Code:
    std::ostream& X::print(std::ostream& out) const
    {
        return out << _val;
    }
    print() could be declared as returning void instead, of course, but here there is nothing wrong with returning a reference even though it is a const member function. Even if the X object was const, only the state of the output stream is changed, the object's state remains unchanged.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  6. #6
    Registered User
    Join Date
    Oct 2007
    Posts
    13
    Quote Originally Posted by Daved View Post
    Code:
    val_class( const BigClass &v ) 
       : _val( v ){ }
    In that code, v is copied into _val. _val itself is not a reference, so when it is initialized with v it calls a copy constructor to copy v into its own data. Once that is done there is no relationship between _val and v, so changes to _val don't affect v (or the BigClass object passed in as v).
    .
    then it really seems to me that the qualifier "const" in "const BigClass &v" is not necessary, since v won't be changed at all by just do initialization of _val to v

  7. #7
    Registered User
    Join Date
    Oct 2007
    Posts
    13
    Quote Originally Posted by AntiScience View Post
    then it really seems to me that the qualifier "const" in "const BigClass &v" is not necessary, since v won't be changed at all by just do initialization of _val to v
    So laserlight answered my question
    thanks to both of you

  8. #8
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    then it really seems to me that the qualifier "const" in "const BigClass &v" is not necessary, since v won't be changed at all by just do initialization of _val to v
    Yes, it is indeed not necessary, but it is good practice. Passing by reference hints that you cannot expect that what you passed will remain unchanged. This is what I mean when I say that it emphasizes that v will be unchanged regardless of what happens to the val_class object and its BigClass member variable.

    For example, we could actually have a case where you pass in v by reference, and lo and behold, it is changed when you change the member variable! This would happen if the member variable was itself a reference to a BigClass object. Instead of being a copy it would be an alias.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  9. #9
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    It is necessary, at least for good design. It isn't strictly necessary from that end, but it is a promise not to change what is passed in as v and not making it const restricts what can be passed to the constructor. You could do this:
    Code:
    val_class( BigClass v ) 
       : _val( v ){ }
    But then the BigClass will be copied twice. Once from the calling argument into v, and then once from v into _val. That's why v is a reference, to avoid the second, unnecessary copy.

    If you make v a non-const reference, then the initialization of _val will work, but it restricts what you can call the val_class constructor with. For example, it is illegal to pass a temporary object to a function that takes a non-const reference. This happens because there is no guarantee that the object won't be modified, and it makes no sense to modify a temporary object that will be thrown away immediately anyway. Making the reference const promises that it won't be modified, so a temporary would be allowed:
    Code:
    val_class( BigClass &v ) 
       : _val( v ){ }
    
    // ...
    
    val_class my_v(BigClass()); // not legal
    The last line in that snippet creates a temporary BigClass and passes it to the val_class constructor. Since the val_class constructor doesn't use a const reference, then there is no promise not to change the temporary, so it is illegal (although some compilers allow it as an extension).

  10. #10
    Registered User
    Join Date
    Oct 2007
    Posts
    13
    Quote Originally Posted by laserlight View Post
    T
    It would be wrong if it returns a reference to a member variable.
    yea, this is important I like this notion:P

  11. #11
    Registered User
    Join Date
    Oct 2007
    Posts
    13
    Quote Originally Posted by Daved View Post
    :
    Code:
    val_class( BigClass &v ) 
       : _val( v ){ }
    
    // ...
    
    val_class my_v(BigClass()); // not legal
    The last line in that snippet creates a temporary BigClass and passes it to the val_class constructor. Since the val_class constructor doesn't use a const reference, then there is no promise not to change the temporary, so it is illegal (although some compilers allow it as an extension).
    I haven't read any material about a temporary object yet....
    but I'll save this for later

  12. #12
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    That's fine. Just follow the rule that if you can make the parameter const, then you should make it const. Technically that applies to all variables, although many people rarely follow the guideline in simple cases.

  13. #13
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Quote Originally Posted by Daved View Post
    That's fine. Just follow the rule that if you can make the parameter const, then you should make it const. Technically that applies to all variables, although many people rarely follow the guideline in simple cases.
    This also helps the compiler tell you when you do something you didn't really mean to do - and that also helps producing less bugs by mistakes in your code. For example swapping your arguments around (admittedly trivial and stupid example):
    Code:
    void classx::blah(const int x)
    {
         x = y;   // we meant y = x
    }
    The compiler will complain about this. But if you didn't make it const, you'd just sit there scratching your head wondering why the code doesn't change y [particularly difficult to find when only some code-paths are wrong, say inside some particular if-statement].

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  14. #14
    Hardware Engineer
    Join Date
    Sep 2001
    Posts
    1,398
    I don't know if this will help or add to the confusion, but Thinking In C++, by Bruce Eckel (FREE online!) contains an entire chapter on const (Vol I, chapter 8).

    ...I was shocked when I read that chapter and realized that this "simple concept" isn't so simple!
    Last edited by DougDbug; 10-19-2007 at 01:48 PM.

  15. #15
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    The parameter to the copy constructor or other constructor needs to take a const reference not just an ordinary reference, because otherwise you can't initialise this object from a const object, or from an unnamed temporary.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Confused with array size
    By desmond5 in forum C Programming
    Replies: 4
    Last Post: 12-04-2007, 05:14 PM
  2. Confused
    By jeev2005 in forum C Programming
    Replies: 5
    Last Post: 06-01-2006, 02:04 PM
  3. why wont this compile?!? :confused:
    By jdude in forum C++ Programming
    Replies: 5
    Last Post: 11-25-2004, 01:13 AM
  4. confused.. in selecting my line of deapth
    By jawwadalam in forum A Brief History of Cprogramming.com
    Replies: 4
    Last Post: 05-04-2003, 01:21 PM
  5. Extern Question, really confused
    By SourceCode in forum C Programming
    Replies: 10
    Last Post: 03-26-2003, 11:11 PM