0

From another question I see this code :

template <typename T>
std::set<T> getUnion(const std::set<T>& a, const std::set<T>& b)
{
  std::set<T> result = a;
  result.insert(b.begin(), b.end());
  return result;
}

Can't we just use below code ? :

template <typename T>
std::set<T> getUnion(std::set<T> a, const std::set<T>& b)
{
  a.insert(b.begin(), b.end());
  return a;
}

Is there any difference ??

I can't understand the reason for using the first approach.

Is the second code inhibit RVO ?

Community
  • 1
  • 1
uchar
  • 2,552
  • 4
  • 29
  • 50
  • 2
    Personally, I don't like this second approach because it essentially leaks implementation details into the interface. The next person reading the header will have to wonder why one parameter is taken by value and the other by const reference, in a function that looks like it should be symmetrical. That said, there are no technical problems with this approach. I seem to recall Herb Sutter promotes it in one of "Exceptional C++" books, claiming that it's more efficient (I don't remember the details as to why). – Igor Tandetnik May 02 '14 at 22:10
  • possible duplicate of [Why is RVO disallowed when returning a parameter?](http://stackoverflow.com/questions/9444485/why-is-rvo-disallowed-when-returning-a-parameter) – juanchopanza May 02 '14 at 22:17
  • @juanchopanza that question use user defined Struct with out move constructor not stl library . So It's not same – uchar May 02 '14 at 22:18
  • 1
    Not an answer, but a much better design would be to pass in an interator to insert entries into a. – Dale Wilson May 02 '14 at 22:19
  • @omid It is a duplicate. You ask if the second code inhibits RVO. It does, as stated in the duplicate. Move has nothing to do with RVO. – juanchopanza May 02 '14 at 22:21

2 Answers2

1

The first version takes a std::set by reference which means you will not get a copy of the argument that is passed. The copy is actually happening in the initialization of result with a. The second takes its argument by value, which means that depending on the value-category of the argument the compiler will call the copy/move constructor. Particularly, if the argument is an lvalue, it is copied, and if an rvalue, moved.

In the first example, the compiler will most likely will discard the copy operation from the return statement, an optimization known as return value optimization (RVO). The second version cannot do this as you are not returning a local variable. So you can see that the second version of the function has to incur at least one extra copy or move operation, while the first deals only with one.

David G
  • 94,763
  • 41
  • 167
  • 253
  • 1
    you mean 'a' is not local in second version ?! – uchar May 02 '14 at 22:21
  • @omid Sure it is local, but the compiler won't consider that. It won't optimize it out unless it knows for a fact that the object itself won't be needed after returning. – David G May 02 '14 at 22:23
  • Why It has one less copy ? In first version we copy too std::set result = a; – uchar May 02 '14 at 22:26
  • @omid You're right, there is a copy there. I had overlooked that. But there is still one more copy going on in the second version. – David G May 02 '14 at 22:29
  • In first one we have one copy. In second one we have one move or one copy. So I think there is no difference – uchar May 02 '14 at 22:32
  • @omid In the first version you have a copy, but in the second one you have either two moves (one move when you pass an rvalue + return) or one move and one copy (when you pass an lvalue). – David G May 02 '14 at 22:35
  • How are you saying there's no difference when there's clearly a difference? Move constructors can have side effects just like copy-constructors. If the compiler doesn't optimize out the move, you could incur overhead from those side effects. No copy/move is better than either. – David G May 02 '14 at 22:43
  • So the second is actually better ? one copy vs 2 move ?(If we pass rvalue ) – uchar May 02 '14 at 22:44
  • sorry I didn't see the edit of your comment . I delete that comment – uchar May 02 '14 at 22:46
  • 1
    @omid Yes, and letting the compiler take care of optimizations is better. Don't prematurely optimize, one can only hurt themselves by doing so. – David G May 02 '14 at 22:47
1

In fact these two functions are equivalent. Either a new set is created by copy of the argument that corresponds to parameter a when the function is called

template <typename T>
std::set<T> getUnion(std::set<T> a, const std::set<T>& b)
{
//...

or it is created inside the body of the function

template <typename T>
std::set<T> getUnion(const std::set<T>& a, const std::set<T>& b)
{
  std::set<T> result = a;
//...

However it is more clear for readers of the code when the function is defined as in the first case

template <typename T>
std::set<T> getUnion(const std::set<T>& a, const std::set<T>& b);

For example the function could be declared as

template <typename T>
std::set<T> getUnion( const std::set<T> &a, std::set<T> b);

could not it?

These variety only arises questions.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • @omid OK, I see. They are equivalent in the sense that for some inputs you get the same outputs, and the caller side sees no difference between the two. But under the hood, there are differences. – juanchopanza May 02 '14 at 23:31