8

There is a set of good rules to determine whether pass by value or const reference

  • If the function intends to change the argument as a side effect, take it by non-const reference.
  • If the function doesn't modify its argument and the argument is of primitive type, take it by value.
  • Otherwise take it by const reference, except in the following cases: If the function would then need to make a copy of the const reference anyway, take it by value.

For constructor as following, how to determine it?

class A
{
public:
    A(string str) : mStr(str) {} // here which is better, 
                                 // pass by value or const reference?

    void setString(string str)  { mStr = str; } // how about here?

private:
    string mStr;
};
user1899020
  • 13,167
  • 21
  • 79
  • 154
  • 2
    The constructor is better on average as what you have with `mStr(std::move(str))`. If you really need to optimize it more, you can still overload it. – chris Sep 06 '13 at 13:30
  • You have to take a copy in any case, don't you? – πάντα ῥεῖ Sep 06 '13 at 13:32
  • @chris I think your `move` way also works for `setString`. If `string` has no move ctor and assignment, which way is better? Thanks. – user1899020 Sep 06 '13 at 13:36
  • With new C++11 rules and move constructors, I don't know any more. But const reference never let me down. – Neil Kirk Sep 06 '13 at 13:39
  • 1
    Yeah, I didn't look at the assignment operator much, but I suppose it would. Anyway, I'd probably say a const reference then. – chris Sep 06 '13 at 13:39
  • 1
    http://stackoverflow.com/questions/10231349/are-the-days-of-passing-const-stdstring-as-a-parameter-over – Paolo Brandoli Sep 06 '13 at 13:40
  • @chris I agree const reference is better if there is no move-ctor. The reason is that `mStr` is an outer variable not an internal variable in the method. – user1899020 Sep 06 '13 at 13:54

4 Answers4

4

In this particular case, and assuming C++11 and move construction/assignment for strings, you should take the argument by value and move it to the member for the constructor.

A::A(string str) : mStr(std::move(str)) {}

The case of the setter is a bit trickier and I am not sure whether you really want/need to optimize every bit of it... If you want to optimize the most you could provide two overloads, one taking an rvalue reference and another taking a const lvalue reference. At any rate, the const lvalue reference is probably a good enough approach:

void A::setString(string const& str) { mStr = str; }

Why the difference?

In the case of the constructor, the member is not yet built, so it is going to need to allocate memory. You can move that memory allocation (and actual copying of the data, but that is the leaser cost) to the interface, so that if the caller has a temporary it can be forwarded without an additional memory allocation.

In the case of assignment the things are a bit more complicated. If the current size of the string is large enough to hold the new value, then no allocation is required, but if the string is not large enough, then it will need to reallocate. If the allocation is moved to the interface (by-value argument), it will be executed always even when it is unnecessary. If the allocation is done inside the function (const reference argument) then for a small set of cases (those where the argument is a temporary that is larger then the current buffer) an allocation that could otherwise have been avoided would be done.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
3

The article you site is not a good reference for software engineering. (It is also likely out of date, given that it talks about move semantics and is dated from 2003.)

The general rule is simple: pass class types by const reference, and other types by value. There are explicit exceptions: in keeping with the conventions of the standard library, it is also usual to pass iterators and functional objects by value.

Anything else is optimization, and shouldn't be undertaken until the profiler says you have to.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
0

In this case it is better to pass argument by const reference. Reason: string is a class type, you don't modify it and it can be arbitrary big.

cpp
  • 3,743
  • 3
  • 24
  • 38
  • 4
    I disagree; if you're copying (or moving) the argument anyway and [want speed, pass by value](http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/). – Angew is no longer proud of SO Sep 06 '13 at 13:35
  • @Angew, that link seems broken. I get a database error. – pattivacek Sep 06 '13 at 13:36
  • I'm not sure the compiler is capable of avoiding a double-copy. – Medinoc Sep 06 '13 at 13:37
  • 1
    @patrickvacek Whoops. I sure hope that's temporary; it's an excellent article. The full title is "Want speed? Pass by value." This seems to be [a mirror](http://joseluisestebanaparicio.blogspot.cz/2010/06/want-speed-pass-by-value.html) – Angew is no longer proud of SO Sep 06 '13 at 13:41
  • I dissagree too: If the argummennt is an lvalue, you want a copy, **but if the argumment is an rvalue, not, you want to move it only**. So I think the best option is to overload the constructor: **One with const reference, and other with rvalue reference**. Note that the in the const reference version, `mStr` is initialized using the copy ctor. – Manu343726 Sep 06 '13 at 13:41
  • 5
    @Manu343726 This can pretty much be achieved without overloading, by taking the argument by value and `std::move()`-ing it when creating the "copy." – Angew is no longer proud of SO Sep 06 '13 at 13:42
  • My comment is about before the edit (You suggest pass-by-value). But the rvalue comment is still valid. – Manu343726 Sep 06 '13 at 13:42
  • @Manu343726: We should all probably agree to not pass by value then ;) – PlasmaHH Sep 06 '13 at 13:43
0

It is always better to use the member initialization list to initialize your values as it provides with the following advantages:

1) The assignment version, creates a default constructor to initialize mStr and then assigned a new value on top of the default-constructed one. Using the MIL avoids this wasted construction because the arguments in the list are used as constructor arguments.

2) It's the only place to initialize constant variables unless these are just intergers which you can use enums in the class. enum T{v=2};

3) It's the place to initialize references.

This is what I would suggest:

#include <iostream>
#include <string>

class A
{
   private:
      std::string mStr;

   public:
      A(std::string str):mStr(str){}

      //if you are using an older version of C++11, then use 
      //std::string &str instead
      inline const void setString(std::string str)
      {
         mStr = str;
      }

      const std::string getString() const
      {
         return mStr;
      }
};

int main()
{
   A a("this is a 1st test.");

   a.setString("this is a 2nd test.");

   std::cout<<a.getString()<<std::endl;
}

Have a look at this: http://www.cplusplus.com/forum/articles/17820/

Constantinos Glynos
  • 2,952
  • 2
  • 14
  • 32