0

So I have a function that needs to take an std::vector as a parameter. I'd like to know the best way to declare the parameter so that the underlying array is not deep-copied, as it could be rather large.

// which should I choose?
void myFunc(std::vector<char>); // 1
void myFunc(std::vector<char>&); // 2
void myFunc(std::vector<char>&&);  // 3
void myFunc(std::vector<char>*) // 4

Which should I choose? Also, I won't be modifying the vector in the function so shouldn't I add const? Should I overload the function and have a combination of these?

dwoodwardgb
  • 180
  • 4
  • 10

2 Answers2

7
  1. If you are going to make a copy of it inside the function anyway:

    void myFunc(std::vector<char>);
    
  2. If you just want to read the argument without copying it:

    void myFunc(const std::vector<char>&);
    
  3. If you want to modify the original vector passed to the function:

    void myFunc(std::vector<char>&);
    
  4. If you want to optimize for rvalues or if you want to move the argument into the function:

    void myFunc(std::vector<char>&&);
    
  5. If you need to be able to signify an optional argument passed by reference:

    void myFunc(const std::vector<char>*);
    
  6. If you need to pass an optional argument that you want to modify if non-nullptr:

    void myFunc(std::vector<char>*);
    
Community
  • 1
  • 1
Emil Laine
  • 41,598
  • 9
  • 101
  • 157
  • According to this answer http://stackoverflow.com/questions/6368690/rvalue-function-overloading option 1 will be optimized for rvalues...so I'm kind of confused. – dwoodwardgb Sep 27 '15 at 18:02
  • @dwoodwardgb Yes. That's exactly why it should be used. You get a function that handles both lvalues and rvalues efficiently. – Emil Laine Sep 27 '15 at 18:02
  • Ok, but how does option 1 work for lvalues? Won't it call the copy constructor for an std::vector? – dwoodwardgb Sep 27 '15 at 18:03
  • Yes, and that's why you only use it if you were going to copy the vector inside the function anyway. – Emil Laine Sep 27 '15 at 18:04
  • How would copying the array inside the function change anything? That is, if I'm going to copy the vector inside the function why would I also want to copy it in the function call? – dwoodwardgb Sep 27 '15 at 18:06
  • If you passed the vector by ref and copied it inside the function, that wouldn't be efficient for rvalues. They'd be constructed in place of the argument and copied inside the function. By passing the vector by value you eliminate that superfluous copying step for rvalues, since the vector can be directly constructed in place of the argument, without needing to copy it after that. – Emil Laine Sep 27 '15 at 18:09
  • Ok, so option 1 works for rvalues, whereas options 2 and 3 might not. – dwoodwardgb Sep 27 '15 at 18:11
  • 1 implies that you omit the copying inside the function at let the function call handle it, so that you copy only once in the end. – Emil Laine Sep 27 '15 at 18:11
  • 3 won't compile for rvalues, whereas 2 will and is perfectly fine you only want to read from it. – Emil Laine Sep 27 '15 at 18:13
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/90742/discussion-between-dwoodwardgb-and-zenith). – dwoodwardgb Sep 27 '15 at 18:16
2

If you don't want to deep copy std::vector:

  • move it into your function:

    // foo() decalaration
    void foo(std::vector<int> v);
    
    // usage example
    std::vector<int> v {0, 1, 2, 3};
    foo(std::move(v));
    // v is moved into foo() and invalid now
    

    You may also return this vector from function in the same manner:

    // foo() decalaration
    std::vector<int> foo(std::vector<int> v) {
        return v.push_back(4), std::move(v);
    }
    
    // usage example
    std::vector<int> v {0, 1, 2, 3};
    v = foo(std::move(v));
    // now v is {0, 1, 2, 3, 4} and no one were deep copied
    

    But note if you don't move it (call foo(v) instead of foo(std::move(v))) then it will be deep copied. Under the hood, parameter v of foo() is just constructed by move-constructor.

  • pass it as reference:

    // foo() declaration
    void foo(std::vector<int>& v);
    

    But now we have a problem: which reference and cv-qualifiers? Well, in general we have 2 types of references and 4 types of cv-qualifiers, altogether 8 declarations:

    void foo(std::vector<int>&);
    void foo(std::vector<int> const&);
    void foo(std::vector<int> volatile&);
    void foo(std::vector<int> const volatile&);
    void foo(std::vector<int>&&);
    void foo(std::vector<int> const&&);
    void foo(std::vector<int> volatile&&);
    void foo(std::vector<int> const volatile&&);
    

    Of course, part of them are useless and should be deleted. But nevertheless too much declarations also known as perfect forwarding problem (actually, there were no rvalue-references when it was a problem so the problem was 2 times smaller).

    For example, if you want to modify v you need 2 functions at least:

    void foo(std::vector<int>&);
    void foo(std::vector<int>&&);
    

    In this case you will be able to call foo() on lvalue objects:

    std::vector<int> v;
    foo(v);
    

    as well as on temporary:

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

    But how to code just one implementation for different reference types and / or cv-qualifiers? Let me introduce universal references:

    template<typename Vector>
    void foo(Vector&& v);
    

    Vector&& is always a reference type and may be deduced into

    • std::vector<int>& if you pass lvalue of type std::vector<int> into foo():

      std::vector<int> v; 
      foo(v); // v is lvalue
      
    • std::vector<int> const& if you pass const lvalue of type std::vector<int>:

      std::vector<int> const v; 
      foo(v); // v is const lvalue
      
    • std::vector<int>&& if you pass rvalue:

      foo(std::vector<int>{0, 1, 2}); // v is rvalue
      
    • etc...

    But in this case you have to check acceptance of type Vector. But that is another story.

And I definitely find no sense to pass pointers instead of references in this case.

Nevermore
  • 1,127
  • 9
  • 12
  • Thanks for the thorough answer! I didn't know about the whole universal references thing. I ended up simply going with myFunc(const std::vector &) because I didn't need to modify the vector at all. – dwoodwardgb Sep 27 '15 at 21:28
  • @dwoodwardgb yea, it depends on purposes. In addition, `auto&&` is universal reference too. – Nevermore Sep 27 '15 at 21:29