eliminate unnecessary copies when calling C++/STL algorithms

前端 未结 2 1273
悲哀的现实
悲哀的现实 2021-02-10 05:12
  • I\'ve coded the following example in order to better illustrate my questions.

  • In the code below, I introduce a function object (i.e., funObj

2条回答
  •  闹比i
    闹比i (楼主)
    2021-02-10 05:42

    1 - I know that most STL algorithms pass their argument by value. However, compared to func, that also passes its input argument by value, the STL algorithms generate an extra copy. What's the reason for this "unnecessary" copy?

    STL algorithms return the function object. This happens so that the mutation on the object will be observable. Your func returns void so that's a copy less.

    • Well, to be precise, generate does not return a thing (see comment by dyp)

    2 - Is there a way to eliminate such "unnecessary" copies?

    Well unnecessary is a bit too strong. The whole point of functors is to be lightweight objects so that a copy wouldn't matter. As for a way, the one you provide (std::ref) will do the job, alas a copy of the std::ref will be generated (your object won't get copied though)

    Another way would be to qualify the call of the algorithm

    then the function object type will be a reference :

    auto fobj1 = funObj();
    
    std::for_each::iterator, std::vector::iterator, 
    funObj&> // this is where the magic happens !!
    (std::begin(v), std::end(v), fobj1);
    

    3 - When calling std::for_each(std::begin(v), std::end(v), funObj()) and func(funObj()) in which scope does temporary object funObj lives, for each case respectively?

    The body of std_for_each is expanded as follows :

    template
      Function for_each(InputIterator first, InputIterator last, Function fn)
    { // 1
      while (first!=last) {
        fn (*first);
        ++first;
      }
      return fn;      // or, since C++11: return move(fn);
    // 2
    }
    

    your function reads

    template
    void func(funObj obj) 
    { // 1.
        obj();  
    // 2.
    }
    

    The comments 1 and 2 mark the lifespan in each case. Note though that if a return value optimization applies (named or unnamed), then the compiler may generate code that places the return value (the function object in for_each) in the stack frame of the caller, so the life span is longer.

    4 - I've tried to use std::ref in order to force pass-by-reference and as you can see the "unnecessary" copy was eliminated. However, when I try to pass a temporary object to std::ref (i.e., std::ref(funObj())) I get a compiler error. Why such kind of statements are illegal?

    std::ref does not work with r-value references (STL code follows):

    template
    void ref(const _Ty&&) = delete;
    

    you need to pass an l-value

    5 - The output was generated using VC++2013. As you can see there's an anomaly when calling std::for_each the destructors of the objects are being called in reversed order. Why is that so?

    6 - When I run the code on Coliru that runs GCC v4.8 the anomaly with destructors is fixed however std::generate doesn't generate an extra copy. Why is that so?

    • Check the settings for each compilation. With optimizations ON (and in Release for VS) copy elision / elimination of extra copies / ignoring non observable behaviors, are possible.

    • Secondly (as far as I can see) in VS 2013 the functor in for_each and the generator in generate are both passed by value (there's no signature accepting an r-value reference) so it would be clearly a matter of copy elision to save the extra copy.

    For what matters, the STL implementation in gcc also has no signatures that accept r-value references (please notify me if one having is spotted)

    template
    _Function
    for_each(_InputIterator __first, _InputIterator __last, _Function __f)
    {
      // concept requirements
      __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
      __glibcxx_requires_valid_range(__first, __last);
      for (; __first != __last; ++__first)
    __f(*__first);
      return _GLIBCXX_MOVE(__f);
    }
    

    so I may be going out on limb on this one and assume, that defining move semantics for your functor has no effect and only compiler optimizations apply to eliminate copies

提交回复
热议问题