3

I have the following code:

std::vector<Info*> filter(int direction)
{
    std::vector<Info*> new_buffer;
    for(std::vector<Info*>::iterator it=m_Buffer.begin();it<m_Buffer.end();it++)
    {
        if(((*it)->direction == direction)
        {
            new_buffer.push_back(*it);
        }
    }

    return new_buffer;
}

std::vector<Info*> &filteredInfo= filter(m_Direction);

Can someone explain what is happening here ? Would the filter method return by value create a temporary and filteredInfo never gets destroyed because its a reference ?

Not sure if I understand correctly. What is the diference between filteredInfo being a reference and not being one in this case ?

Adrian
  • 19,440
  • 34
  • 112
  • 219

3 Answers3

6

Your compiler should complain of that code.

This statement:

std::vector<Info*> &filteredInfo= filter(m_Direction);

is a bad idea where filter is:

std::vector<Info*> filter(int direction);

You are trying to create a reference to a temporary object. Even if it succeeds with your compiler, its illegal.

You should use:

std::vector<Info*> filteredInfo= filter(m_Direction);

Its as efficient as you want. Either a move operation (C++11) will happen there or Return Value Optimization will kick in. For your implementation of filter, it should be RVO on optimized builds (it depends on your compiler quality though) .

However, you should note that you are copying raw pointers into your vector, I hope you have a correct ownership model? If not, I advice you to use a smart pointer.

Community
  • 1
  • 1
WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
2

Here is what happens:

  1. std::vector<Info*> new_buffer; creates an object locally.
  2. return new_buffer; moves new_buffer to a temporary object when filter(m_Direction) is called.
  3. Now if you call std::vector<Info*> filteredInfo= filter(m_Direction); the temprary object will be moved to filteredInfo so there is no unnecessary copies and it's the most efficient way.
  4. But, if you call std::vector<Info*> &filteredInfo= filter(m_Direction); then filteredInfo is bound to a temporary object, which is a terrible idea and most compilers will complain about this.
Eissa N.
  • 1,695
  • 11
  • 18
2

Here you're correctly puzzled because there are two independent weird facts mixing in:

  • Your compiler allows a non-const reference to be bound to a temporary. This historically was a mistake in Microsoft compilers and is not permitted by the standard. That code should not compile.
  • The standard however, strangely enough, actually allows binding const references to temporaries and has a special rule for that: the temporary object will not be destroyed immediately (like it would happen normally) but its life will be extended to the life of the reference.

In code:

std::vector<int> foo() {
    std::vector<int> x{1,2,3};
    return x;
}

int main() {
    const std::vector<int>& x = foo(); // legal
    for (auto& item : x) {
        std::cout << x << std::endl;
    }
}

The reason for this apparently absurd rule about binding const references to temporaries is that in C++ there is a very common "pattern"(1) of passing const references instead of values for parameters, even when identity is irrelevant. If you combine this (anti)-pattern with implicit conversion what happens is that for example:

void foo(const std::string& x) { ... }

wouldn't be callable with

foo("Hey, you");

without the special rule, because the const char * (literal) is implicitly converted to a temporary std::string and passed as parameter bound to a const reference.

(1) The pattern is indeed quite bad from a philosophical point of view because a value is a value and a reference is a reference: the two are logically distinct concepts. A const reference is not a value and confusing the two can be the source of very subtle bugs. C++ however is performance-obsessed and, especially before move semantics, passing const references was considered a "smart" way of passing values, despite being a problem because of lifetime and aliasing issues and for making things harder for the optimizer. With a modern compiler passing a reference should be used only for "big" objects, especially ones that are not constructed on the fly to be passed or when you're actually interested in object identity and not in just object value.

Community
  • 1
  • 1
6502
  • 112,025
  • 15
  • 165
  • 265