Czy mogę zainicjować listę std :: vector z doskonałym przekazywaniem elementów?

14

Zauważyłem, że zbiorczą listę initalization std :: vector wykonuje skopiować inicjalizacji gdy ruch jest bardziej dotyczy. W tym samym czasie wiele lokacebacków robi to, co chcę.

Mogłem tylko wymyślić to niedoskonałe rozwiązanie pisania funkcji szablonu init_emplace_vector. Jest to jednak optymalne tylko dla nieprecyzyjnych konstruktorów pojedynczej wartości .

template <typename T, typename... Args>
std::vector<T> init_emplace_vector(Args&&... args)
{
  std::vector<T> vec;
  vec.reserve(sizeof...(Args));  // by suggestion from user: eerorika
  (vec.emplace_back(std::forward<Args>(args)), ...);  // C++17
  return vec;
}

Pytanie

Czy naprawdę muszę korzystać z umiejscowienia_docelowego , aby zainicjować std :: vector tak skutecznie, jak to możliwe?

// an integer passed to large is actually the size of the resource
std::vector<large> v_init {
  1000,  // instance of class "large" is copied
  1001,  // copied
  1002,  // copied
};

std::vector<large> v_emplaced;
v_emplaced.emplace_back(1000);  // moved
v_emplaced.emplace_back(1001);  // moved
v_emplaced.emplace_back(1002);  // moved

std::vector<large> v_init_emplace = init_emplace_vector<large>(
  1000,   // moved
  1001,   // moved
  1002    // moved
);

Wynik

Klasa largegeneruje informacje o kopiach / przeniesieniach (implementacja poniżej), więc wyniki mojego programu to:

- initializer
large copy
large copy
large copy
- emplace_back
large move
large move
large move
- init_emplace_vector
large move
large move
large move

Implementacja dużej klasy

Moja implementacja largeto po prostu typ kopiowalny / ruchomy z dużym zasobem, który ostrzega przed kopiowaniem / przenoszeniem.

struct large
{
  large(std::size_t size) : size(size), data(new int[size]) {}

  large(const large& rhs) : size(rhs.size), data(new int[rhs.size])
  {
    std::copy(rhs.data, rhs.data + rhs.size, data);
    std::puts("large copy");
  }

  large(large&& rhs) noexcept : size(rhs.size), data(rhs.data)
  {
    rhs.size = 0;
    rhs.data = nullptr;
    std::puts("large move");
  }

  large& operator=(large rhs) noexcept
  {
    std::swap(*this, rhs);
    return *this;
  }

  ~large() { delete[] data; }

  int* data;
  std::size_t size;
};

Edytować

Korzystając z rezerwy, nie ma kopiowania ani przenoszenia. Wywoływany large::large(std::size_t)jest tylko konstruktor. Prawdziwe miejsce pracy.

ponownie połączyć
źródło
1
To nie jest inicjalizacja agregacji, wywołujesz konstruktor, który bierze std::initializer_list.
super
Konstruktory std :: vector są mylącym bałaganem IMHO, a gdybym był tobą, naprawdę unikałbym tego, gdybym absolutnie nie musiał. Dodatkowo, jeśli znasz wcześniej zawartość wektorów (wydaje się, że tak może być) - zastanów się std::array.
einpoklum
1
operator=Która niszczy źródła jest dość nietypowa i może powodować nieoczekiwane problemy.
alain
1
init_emplace_vectormożna ulepszyć za pomocąvec.reserve(sizeof...(Args))
Indiana Kernick
1
@alain operator=nie niszczy źródła. To idiom z kopiowaniem i zamianą.
ponownie

Odpowiedzi:

11

Czy mogę agregować-inicjować std :: vector ...

Nie. std::vectorNie jest agregacją, więc nie można jej zainicjować.

Zamiast tego możesz mieć na myśli inicjalizację listy, w którym to przypadku:

Czy mogę [list-initialize] std :: vector z doskonałym przekazywaniem elementów?

Nie. Inicjalizacja listy używa std::initializer_listkonstruktora i std::initializer_listkopiuje jego argumenty.

Twoje init_emplace_vectorwydaje się być przyzwoitym rozwiązaniem, chociaż można je poprawić, rezerwując pamięć przed umieszczeniem elementów.

eerorika
źródło
1
Dzięki za poprawienie mnie. Edycja Dobry punkt z rezerwą.
ponownie