Чи можу я перерахувати-ініціалізувати std :: vector із ідеальним пересиланням елементів?


14

Я помітив, що ініталізація сукупного списку std :: vector виконує ініціалізацію копіювання, коли переміщення є більш застосовною. У той же час кілька emplace_backs роблять те, що я хочу.

Я міг тільки придумати це недосконале рішення написання функції шаблону init_emplace_vector. Однак це оптимально лише для не явних однозначних конструкторів.

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;
}

Питання

Чи дійсно мені потрібно використовувати emplace_back , щоб максимально ефективно ініціалізувати std :: vector?

// 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
);

Вихідні дані

Клас largeвидає інформацію про копії / ходи (реалізація нижче), і таким чином вихід моєї програми:

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

Реалізація великого класу

Моя реалізація - largeце просто копіюваний / рухомий тип, який містить великий ресурс, який попереджає про копіювання / переміщення.

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;
};

Редагувати

Використовуючи резерв, немає копії та переміщення. Викликається лише large::large(std::size_t)конструктор. Справжнє місце.


1
Це не агрегатна ініціалізація, ви викликаєте конструктор, який приймає std::initializer_list.
супер

std :: конструктори вектора - це заплутаний безлад IMHO, і якби я був ти, я б дійсно уникав потрапляння в нього, якщо мені зовсім не доведеться. Плюс, якщо ви заздалегідь знаєте вміст векторів (мабуть, це може бути так) - врахуйте std::array.
einpoklum

1
An operator=Який руйнує джерело досить незвично і може викликати несподівані проблеми.
алайн

1
init_emplace_vector можна було б покращити за допомогою vec.reserve(sizeof...(Args))
Індіана Кернік

1
@alain operator=не знищує джерело. Це ідіома копіювання-своп.
повторно

Відповіді:


11

Чи можу я об'єднати-ініціалізувати std :: vector ...

Ні. std::vectorНе є сукупністю, тому її не можна об'єднати ініціалізувати.

Натомість ви можете мати на увазі ініціалізацію списку:

Чи можу я [список-ініціалізувати] std :: вектор із ідеальним пересиланням елементів?

Ні. Список-ініціалізація використовує std::initializer_listконструктор і std::initializer_listкопіює його аргументи.

Ви, init_emplace_vectorздається, є гідним рішенням, хоча його можна покращити, резервуючи пам'ять перед тим, як замінити елементи.


1
Дякуємо, що виправили мене. Відредаговано. Гарний момент із резервом.
повторно
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.