1

I have a defined class A and an std::vector vec that stores a series of A instances. I attempt to write a function that appends an arbitrary number of A instances to vec.

Should I use vec.reserve() along with vec.push_back() since constantly resizing vector is expensive? Or, should I use vec.insert()?

Currently, I'm not sure if I should design the function to accept a variable number of arguments using variadic functions or a vector of A instances that I'd like to combine and then concatenate with vec?

What is an efficient way to design this function (speed is critical and A is a complex class)?

Nicholas
  • 2,560
  • 2
  • 31
  • 58
  • `reserve()` simply allocates a memory block to hold the elements, and sets the `capacity` accordingly, but you still need to `insert()`/`push_back()` values to actually construct elements inside the allocated memory and update the `size`. Where are the values coming from to begin with? Can you provide a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) showing what you are trying to accomplish? If `A` is complex, and expensive to construct/copy, maybe consider using a vector of `A*` pointers instead, which will be faster to pass around. – Remy Lebeau Oct 27 '16 at 00:39
  • This question would benefit from an example. My first instinct was `vec.resize(vec.size() + increment, newA)`. – MSalters Oct 27 '16 at 13:34

2 Answers2

1

The following should cover common cases, I think. The rref_capture trick is from this answer. The point of it all is that the values are moved whenever possible.

You can also use a variadic template version, as given in the other answer.

// https://github.com/KubaO/stackoverflown/tree/master/questions/vector-append-40274282
#include <array>
#include <cassert>
#include <initializer_list>
#include <iterator>
#include <type_traits>
#include <vector>

template<typename T>
class rref_capture {
   T* ptr;
public:
   rref_capture(T&& x) : ptr(&x) {}
   operator T&& () const { return std::move(*ptr); } // restitute rvalue ref
};

template <typename T>
void append(std::vector<T> & v,
            typename std::decay<std::initializer_list<rref_capture<T>>>::type u) {
   v.reserve(v.size() + u.size());
   for (auto && item : u)
      v.push_back(std::move(item));
}

template <typename T, typename U>
void append(std::vector<T> & v, U && u) {
   v.reserve(v.size() + std::distance(std::begin(u), std::end(u)));
   for (auto & item : u)
      v.push_back(std::move(item));
}

template <typename T, typename U>
void append(std::vector<T> & v, U & u) {
   v.reserve(v.size() + std::distance(std::begin(u), std::end(u)));
   for (auto & item : u)
      v.push_back(item);
}

struct A {
   static int copies, moves;
   A() {}
   A(A&&) { moves++; }
   A(const A&) { copies++; }
   A& operator=(const A&) { copies++; return *this; }
   static void reset() { A::copies = 0; A::moves = 0; }
};
int A::copies, A::moves;

int main() {
   std::vector<A> vec;
   vec.reserve(100);

   A::reset();
   append(vec, {A(), A()});
   assert(A::copies == 0 && A::moves == 2 && vec.size() == 2);

   auto vec2 = vec;
   A::reset();
   append(vec, vec2);
   assert(A::copies == 2 && A::moves == 0 && vec.size() == 4);

   A::reset();
   append(vec, std::move(vec2));
   assert(A::copies == 0 && A::moves == 2 && vec.size() == 6);

   A::reset();
   append(vec, std::array<A,2>{A(), A()});
   assert(A::copies == 0 && A::moves == 2 && vec.size() == 8);

   const std::vector<A> cvec{2};
   A::reset();
   append(vec, cvec);
   assert(A::copies == 2 && A::moves == 0 && vec.size() == 10);

   A arr[2];
   A::reset();
   append(vec, arr);
   assert(A::copies == 2 && A::moves == 0 && vec.size() == 12);
}
Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
0

Well... I suppose you could use reserve() one time and then add the single element using push_back().

The following example use int instead of class A but should give the idea

#include <vector>
#include <iostream>

template <typename T>
void appendT (std::vector<T> & v)
 { }

template <typename T, typename ... Ts>
void appendT (std::vector<T> & v, T t, Ts ... ts)
 { 
   v.push_back(t);

   appendT(v, ts...);
 }

template <typename T, typename ... Ts>
void appendTs (std::vector<T> & v, Ts ... ts)
 {
   v.reserve(v.size() + sizeof...(Ts));

   appendT(v, ts...);
 }

int main()
 {
   std::vector<int> v { 2, 3, 5 };

   appendTs(v, 7, 11, 13, 17);

   for ( auto const & i : v )
      std::cout << ' ' << i; // print " 2 3 5 7 11 13 17"

   std::cout << std::endl;
 }

If you don't like the recursive solution, you can write an appendTs() that do all the works (but I don't know how to avoid the annoing "warning: unused variable 'unused'" I know how to avoid the warning but I don't know if it's a good idea Kuba Ober suggested me an elegant way to avoid the warning)

#include <vector>
#include <iostream>

template <typename T, typename ... Ts>
void appendTs (std::vector<T> & v, Ts ... ts)
 {
   v.reserve(v.size() + sizeof...(Ts));

   // the first '0' is to avoid an error when sizeof...(Ts) is zero
   char unused[] { '0', (v.push_back(ts), '0')... };

   // the following statement is to avoid an annoing "unused variable
   // 'unused'" warning (thanks Kuba Ober)
   (void)unused;
 }

int main()
 {
   std::vector<int> v { 2, 3, 5 };

   appendTs(v, 7, 11, 13, 17);

   for ( auto const & i : v )
      std::cout << ' ' << i; // print " 2 3 5 7 11 13 17"

   std::cout << std::endl;
 }
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
max66
  • 65,235
  • 10
  • 71
  • 111