How safe is returning a value by reference?

Discussion in 'C++' started by davidkevin, Jul 28, 2012.

  1. davidkevin

    davidkevin Guest

    Hi,
    I have seen an interesting example of applications of returning a value by reference from method. But is it really safe? If not could you give me someclear examples which will show when it is unsafe? Stroustrup has given in TC++PL among others following sample:

    class Matrix {
    friend Matrix& operator+(const Matrix&, const Matrix&)
    };

    He have claimed that above code is proper but it causes problems with memory allocation. I am not sure what he means.

    He has stated that:
    1. a reference to a result will be passed outside the function as a reference to a value passed from a function, so that value can't be an automatic variable.
    2. Operator is often used in an expression more than once so it can't be a local static variable.
    3. Copying a passed value is usually cheaper (with respect to time of execution, size of code and size of data).

    I would like to know what are reasons for which above sentences holds. Again, I would be grateful for clear examples.

    Next - Stroustrup claim that passing by reference could cause an improvement of efficiency (however his third from above statements shows that not necessarily). Ok, but I wonder what happens in code like that:

    Matrix a, b;
    Matrix c;
    c=a+b;

    If operator+ returns a value by value then that value is returned by returnthis ; inside operator+ definition. Then a compiler can directly assign c to newly created object.

    If operator+ returns a value by a reference then a copy has to be created -in other case c would be a same object which a+b is. But syntax of the assignment denies it - a reference is nothing other than a synonym for an object so above instruction has to mean that a value is assigned (and not object).

    So as far as I understand when an assignment or an initialization is executed returning a value by a reference has no impact for capacity. Am I right?

    Later, Stroustrup has given an example of the technique about which he has said that it causes that a result is not copied:

    const int max_matrix_tem = 7;

    Matrix& give_matrix_tem() {
    static int nbuf = 0;
    static Matrix buf[max_matrix_tem] nbuf = 0;
    return buf[nbuf++];
    }

    Matrix& operator+(const Matrix& arg1, const Matrix& arg2) {
    Matrix& res = give_matrix_tem();
    //...
    return tem;
    }

    As far as I understand above code should help to improve efficiency if we have
    an expression in which there is more than one operator+. But when an assignment to give_matrix_tem should be executed? What should be assigned to give_matrix_tem? It looks that in the situation in which buf is full filled a value which give_matrix_tem() will return next will be old one. And how above code make that a copying of value is avoided?

    Thanks in advance for responses,
    Greetings.
     
    davidkevin, Jul 28, 2012
    #1
    1. Advertisements

  2. The (1) seems convoluted too much (did you re-type it from any of his
    books, by memory?). (1) You cannot return a reference to an automatic
    object because by the time you get to use that reference, the object has
    been already destroyed. (2) You cannot return a reference to a static
    object inside the op+ function because the function could be used more
    than once in the same expression, and hence needs to be safely re-entrant.
    If operator+ has a 'this' to return, it's a non-static member function,
    yes? To be that it has to have only one explicit argument, the
    right-hand side of the + expression, yes? Then what does it change, the
    left-hand side? IOW, in the expression a+b, the matrix 'a' will be
    changed? That's absurd.

    So, conclusion then is that operator+ cannot 'return this'. It can only
    be a friend function or a const member function that *still* returns a
    new object, but not 'this'.
    Can you rephrase that? I have hard time following your thought.

    There are certain things you might want to read up on. First, RVO
    (return value optimization). The expression c=a+b causes two (usually)
    function calls, op+ and op=. If op+ returns a temporary, then the
    compiler could optimize away the call to op= to actually use the 'c'
    object as the place where the temporary is created for the result of the
    addition. So, no copying is involved. Second, perhaps you should get
    familiar with C++0x feature called "move assignment", which could help
    in this case, giving another way for the compiler to optimize away
    copying if it's not necessary since the object *from* which the contents
    are copied during the assignment, is temporary and doesn't have to survive.
    What's "impact for capacity"? Please define that before I can answer
    your "am I right" question.
    Something is not right in that code. A semicolon seems missing somewhere.
    Yes, a temporary storage is used for the result of op+. That storage is
    allocated once and reused, in hopes that none of functions that make use
    of that storage are called in some expressions more than 7 times.
    Some time inside the op+ after you got the 'res' reference.
    Nothing. You should think of terms of 'res' when implementing the op+
    function that uses 'give_matrix_tem'.
    Returning a reference to an existing object does not involve copying.
    Since 'res' in op+ is a reference to an existing storage object (one of
    the elements of the 'buf' array), no copying happens. Or maybe I don't
    understand what you're asking...

    V
     
    Victor Bazarov, Jul 28, 2012
    #2
    1. Advertisements

  3. davidkevin

    davidkevin Guest

    W dniu sobota, 28 lipca 2012 23:15:15 UTC+2 użytkownik Victor Bazarov napisał:
    The above text is taken from "The C++ Programming Language", Seventh Edition, chapter 11.6 (Operator overloading: Large objects). I had that book before my eyes when I was writing my post. But it it is not rewritten a character to a character - I have a polish edition.
    Understand I things proper that it means that in the situation in which I have
    a variable which was declared in method m() I can not return a reference tothat object? (It seems to me that more complex situations could lead to such error but that example has only show about what the problem is).
    OK. But how returning the reference to the static object can make that re-entrant will not be safe?
    Yes, I have made a terrible mistake. I did mean a situation in which a new
    object is returned (which is a sum of a and b).
    I want to know if returning a reference make it possible to improve an efficiency of calculations in instructions like:

    c=a+b;

    In other words: if we have instructions:

    c=a+b; //operator+ returns by a value
    f=d+e; //operator+ returns by a reference

    will one of those more efficient than other? For me it seems that not. In
    fist case return instruction inside operator+ will make that one copy of object will be created. When the assignment will be executed a next copy has not be created because a+b is temporary so we can use it directly (and I think that every good compiler will not make that unnecessary copy). In second case a value is returned by a reference so object is not copied here. Butcopy has to be executed in order to make the assignment possible.
    So in both cases one copy is created. So there is no reason for which one version should be more efficient than other. Am I right?
    Have I used a word "capacity" incorrectly? What I mean was that returning areference has no impact for efficiency.
    const int max_matrix_tem = 7;

    Matrix& give_matrix_tem() {
    static int nbuf = 0;
    static Matrix buf[max_matrix_tem] nbuf = 0;
    if (nbuf==max_matrix_tem) nbuf = 0; //missed line
    return buf[nbuf++];
    }
    That conception (using a buffer of static objects) is still not very clear for me. What is a reason for which operator+ know that give_matrix_tem willgive him an object which it that operator really needs?
     
    davidkevin, Jul 29, 2012
    #3
  4. davidkevin

    James Kanze Guest

    I think you (the original poster) have slightly misunderstood.
    The above code is "correct", in that it doesn't violate any of
    the syntactic rules of C++. It's not correct because of
    lifetime of object issues; the reference you return refers to an
    object which ceases to exist when you return.

    I'm sure he expressed it more clearly than that. The returned
    reference refers to a local variable, which ceases to exist once
    you've left the function. (This is called a dangling
    reference.)
    Reentrant isn't really the word which corresponds, since
    reentrant refers to being in the same function several times at
    once. But I can't find a good word to specify the quality
    needed.
    Not in this case (assignment). The c object has already been
    constructed, so the compiler cannot use it for another object
    which will be newly constructed.
    No copy construction is involved. The compiler cannot optimize
    away copy assignment (or at least, not easily).
    Introducing move semantics at the point where the original
    poster is may be a bit premature. They're an advanced
    technique, which can be ignored by most programmers.
    He seems to be inventing a special vocabulary of his own.
    Replace "capacity" by "storage" or "storage duration", and it
    makes a little more sense.
    I'd guess that the second "nbuf = 0" is noise, and shouldn't be
    there. Without it, this was a classic technique to avoid deep
    copy of return values. It's also a very dangerous techique: it
    works for up to 7 (in this case) return values, and then fails
    in a difficult to detect manner. (Difficult to detect, because
    the failure only occurs in complex expressions, where one of the
    return values sometimes gets overwritten.)

    This is a technique which should only be used with extreme
    caution, in very restricted contexts, by programmers who really
    know what they're doing. If Stroustrup actually recommends this
    in his recent book (the one for beginners), then I'm a bit
    disappointed.

    (The technique can result in a significant improvement in
    performance. But there are other techniques which can be used
    to avoid the copy as well.)
    And in the hopes that the code will never be used in a
    multithreaded environment.
     
    James Kanze, Jul 29, 2012
    #4
  5. davidkevin

    SG Guest

    Am Sonntag, 29. Juli 2012 10:30:30 UTC+2 schrieb (unbekannt):
    If it's an _automatic_ object (an object stored in the automatic memory in the scope of the function) it will cease to exist once the execution returns to the caller. Returnung a reference to this object is pointless since you cannot access it anymore through that reference outside the function because the object does not exist anymore.

    Well, you "can" do lots of things. But you should not because most of it does not make sense and/or is a bad idea.
    Sorry, what?
    What copy do you mean?
    This does not make sense to me. First, you say a copy has to be created then you say that a decent compiler will optimize it away. There is nothing a compiler can do about assignments. If you have an assignment, the appropriate operator= function gets excecuted. That's it.
    In my opinion, efficiency matters little if the "faster" version is not re-entrant or cannot be used in a multi-threaded environment or even returns dangling references.
    Please don't ever write horrible code like this.

    If you're concerned about efficiency, please look elsewhere. For example:
    - move semantics
    - copy on write.
    - expression templates (lazy evaluation)

    Cheers!
     
    SG, Jul 29, 2012
    #5
  6. davidkevin

    James Kanze Guest

    Don't. Regretfully, translations of technical books are almost
    always miserable, and frequently misleading. Stroustrup is one
    of the rare authors who understands the problem, and does what
    he can to fix it, when he is made aware of it. But globally,
    you're better off struggling in English with the original. You
    won't have to deal with misleading translations, and when you do
    post a question in an English speaking forum, you'll be able to
    use the correct vocabulary, instead of trying to guess the
    original word that the translator translated with whatever in
    your language. And of course, the more you read in English,
    the less you'll have to struggle.

    (I really regret having to give this advice, because I think you
    should be able to use your own language. But the reality is
    that you can't.)
    I think you need some clarification with regards to the language
    you're using. All *objects* have a specific lifetime. (A
    variable is a name bound to an object.) If you define a
    non-static variable with local scope (within a block of code),
    the object has something known as "automatic" lifetime, which
    means it ceases to exist when the name goes out of scope.
    (Unless, of course, the variable has reference type, in which
    case, with one major exception which doesn't concern us here, it
    has no effect on the object lifetime.)

    A reference is not an object, but creates another way of
    referring to an existing object. If the lifetime of the
    reference exceeds that of the object, you have a dangling
    reference. Which is undefined behavior---another way of saying
    that anything might happen.
    In more complicated expressions, like "(a+b) * (c+d)", the
    return value of "a+b" and of "c+d" refer to the same object. If
    the two return values are supposed to have different values, one
    of them will have been overwritten when you get to the *
    operator.
    It might, or it might not. In the general case, if copying a
    Matrix is expensive, it probably will improve efficiency. At
    the cost of giving incorrect results in more complicated
    expressions.
    The assignment will still copy. On the other hand, the return
    value will not be copied from any local variable.

    Most compilers today implement some form the return value
    optimization, which will avoid the copy in well written code.
    (In at least one compiler, "well written code" means no more
    than a single return statement in the function.)
    Without the return value optimization, return by value would
    result in a copy in the function; the return value will be
    copied from a local variable or a temporary into where ever the
    compiler puts class type return values. (The memory used for
    local variables typically disappears when you return from the
    function.) The return value optimization means that this copy
    is suppressed.

    [...]
    There's still too much on the preceding line.
    Don't worry about it. It's an advanced technique, to be learned
    only after you've mastered the basic lifetime issues. The basic
    rule is:

    -- return a reference when you want to allow access to data
    held by your object (e.g. something like the operator[] on a
    vector).

    -- return a value in all other cases.

    -- pass class types by reference to const, all other types by
    value.

    -- and don't use references anywhere but in the above cases.

    That's sufficient for all but the most advanced programmers. (I
    can't remember the last time I did anything else, and I've 30
    years experience in C++, much of it in very low level,
    performance oriented code.)
     
    James Kanze, Jul 29, 2012
    #6
  7. davidkevin

    davidkevin Guest

    OK, thanks to all for the discussion, every post was valuable for me but last post by James Kanze make things completely clear and I have understood already how the technique of buffering static objects could help in the improvement of calculations efficiency.

    BTW: James Kanze, I believe that a book which you described as a "book for beginners" is "Programming: Principles and Practice using C++" but it isn'tone which I use now. The book which I read is "The C++ Programming Language". It is the fact that Stroustrup has shown a sample of the technique mentioned above but he explicitly stated that if somebody will write an expression which would require more temporary variables than max_matrix_tem then he is asking about enter into real troubles.

    Greetings.
     
    davidkevin, Jul 30, 2012
    #7
  8. davidkevin

    James Kanze Guest

    That's the one I was referring to. If you've no great
    experience programming, it's by far the best book I've seen for
    starting.
    That's an excellent reference, but it's more oriented to
    advanced programmers (IMHO, anyway).
    Yes. Stroustrup tends to understand these sort of things. It's
    a useful technique in certain restricted contexts. It was also
    much abused in earlier days (before RVO and NRVO were well
    known).
     
    James Kanze, Jul 30, 2012
    #8
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.