By reference or by value parameter?

Feb 10, 2020 at 12:31am
Hi everybody,

If I have a recursive function having a signature like this one below, I'm wondering if the recursive call will pass the entire vector or if it's just a reference to it.

My concern is about memory usage... well if it passes a huge vector that way multiple times, is it correct?

If not, what's the better way (or syntax to use) not to pass the entire vector every time?

1
2
3
4
5
int test(const std::vector<int>& items) {
...
    result = test(items);
...
}


Thanks
Franck
Last edited on Feb 10, 2020 at 12:34am
Feb 10, 2020 at 12:49am
It just keeps passing a reference, so you're golden.
Although it's hard to see how your recursion is going to make any progress unless there's another parameter.
Last edited on Feb 10, 2020 at 12:51am
Feb 10, 2020 at 12:49am
It's just a reference to it, because you use '& items' but not 'items'.(That's good, it takes a long time to copy a big vector when you use 'items')
Feb 10, 2020 at 12:51am
The & in the function signature means "by reference". So, no worries, you're correctly passing it by reference.

You are correct that you wouldn't want to be doing copies, but more so because that would just be wasted computation than wasted stack space, since the majority of an std::vector is on the heap anyway.

But when working recursively, you always want to be sure its never used in such a way where it's called 100s of times recursively, because that could cause a stack overflow regardless of how many parameters you're passing. As a loose rule, I prefer iteration unless the recursive solution is a magnitude more clear and easy to understand.
Last edited on Feb 10, 2020 at 12:54am
Feb 10, 2020 at 1:00am
you always want to be sure its never used in such a way where it's called 100s of times recursively, because that could cause a stack overflow regardless of how many parameters you're passing

As long as you write the routine using tail recursion, and you know the compiler (or language standard) guarantees tail recursion optimization, then it only uses one stack frame (i.e., it's optimized into an iterative routine).

E.g.,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <vector>

// Number of stack frames equals length of vector + 1.
int sum(const std::vector<int>& v, size_t i = 0) {
    if (i == v.size()) return 0;
    return v[i] + sum(v, i + 1);
}

// Number of stack frames equals 1.
int sum_tail_recursive(const std::vector<int>& v, size_t i = 0, int subsum = 0) {
    if (i == v.size()) return subsum;
    return sum_tail_recursive(v, i + 1, subsum + v[i]);
}

int main() {
    std::vector<int> v {1, 2, 3, 4, 5};
    std::cout << sum(v) << '\n';
    std::cout << sum_tail_recursive(v) << '\n';
}

Obviously for simply summing a vector, this is ridiculous.
Last edited on Feb 10, 2020 at 1:01am
Feb 10, 2020 at 2:14am
> what's the better way (or syntax to use) not to pass the entire vector every time?

This is the usual way: pass a pair of iterators to identify the beginning and the end of the range.
(Copying an iterator is inexpensive.)

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <list>
#include <string>

// iterators: https://cal-linux.com/tutorials/STL.html
template < typename ITERATOR >
void sel_sort( ITERATOR begin, ITERATOR end )
{
    if( begin != end ) // if the range is not empty
    {
        // bring the smallest element to the front
        // https://en.cppreference.com/w/cpp/algorithm/iter_swap
        // https://en.cppreference.com/w/cpp/algorithm/min_element
        std::iter_swap( begin, std::min_element( begin, end ) ) ;
        
        // sort the elements after the the smallest one in the front 
        sel_sort( ++begin, end ) ;
    }
}

template< typename RANGE > void sel_sort( RANGE& range )
{
    // https://en.cppreference.com/w/cpp/iterator/begin
    sel_sort( std::begin(range), std::end(range) ) ;
}

template< typename RANGE > void print( const RANGE& range )
{
    // range based loop: http://www.stroustrup.com/C++11FAQ.html#for
    // auto: http://www.stroustrup.com/C++11FAQ.html#auto
    for( const auto& v : range ) std::cout << v << ' ' ;
    std::cout << '\n' ;
}

int main ()
{
    std::vector<int> a { 5, 1, 9, 3, 2, 7, 6, 8, 0, 4 } ;
    print(a) ;
    sel_sort(a) ;
    print(a) ;

    std::list<std::string> b { "def", "jkl", "abc", "mno", "ghi", "stu", "pqr" } ;
    print(b) ;
    sel_sort(b) ;
    print(b) ;
}

http://coliru.stacked-crooked.com/a/f892ebd167683ffa
Feb 10, 2020 at 2:39am
Thanks a lot everybody!

@dutch: You're right, it was just for the purpose of the question so I simplified a lot. Here's a little more realistic view of what I'm doing...

@JLBorges: Is what I did below is somehow like what you mentioned?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
std::tuple<int, int, int> Rec(const std::vector<int>& price, int begin, int end, int diff) {

    if (begin >= end) {
        return std::tuple<int, int, int>(begin, end, 0);
    }
    if (begin + 1 == end) {
        return std::tuple<int, int, int>(begin, end, (price[end] - price[begin]));
    }

    int center = (begin + (end - begin) / 2);
    std::tuple<int, int, int> left;
    std::tuple<int, int, int> right;

	//odd
    if (((end - begin) % 2) == 0) {
        left = Rec(price, begin, center - 1, diff);
        right = Rec(price, center, end, diff);
    }
	//even
    else {
        left = Rec(price, begin, center, diff);
        right = Rec(price, center + 1, end, diff);
    }
...


Franck

Feb 10, 2020 at 3:02am
> Is what I did below is somehow like what you mentioned?

It is somewhat similar.

You identify a range by passing the vector, the position of the first item and the position of the last item.

With iterators, there is no need to pass the vector: the iterator knows what item it is 'pointing' to. The end for the iterator version is not the last item, but a fictitious 'one past the last' item.
"The iterator to the end of a container or array is defined as the element following the last valid element"
https://en.cppreference.com/w/cpp/iterator/end
Feb 10, 2020 at 3:08am
OK thank you again, I'll read a bit about that.

Feb 10, 2020 at 1:28pm
Passing the iterator isn't significantly less memory intensive than passing a pointer to the vector and besides the loss of intuitive correctness, ie freedom from vague obscurity, it runs the risk of unnecessary ambiguity with other vectors of the same type, and adds equally unnecessary complexity give that the vector carries with it all the necessary information regard start, general access, size and end points.
Topic archived. No new replies allowed.