¿Puedo listar-inicializar std :: vector con reenvío perfecto de los elementos?

14

Noté que la inicialización de la lista agregada de std :: vector realiza la inicialización de copia cuando el movimiento es más aplicable. Al mismo tiempo, múltiples emplace_backs hacen lo que quiero.

Solo pude encontrar esta solución imperfecta de escribir una función de plantilla init_emplace_vector. Sin embargo, solo es óptimo para constructores de valor único no explícitos .

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

Pregunta

¿Realmente necesito usar emplace_back para inicializar std :: vector de la manera más eficiente posible?

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

Salida

La clase largeproduce información sobre copias / movimientos (implementación a continuación) y, por lo tanto, el resultado de mi programa es:

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

Implementación de clase grande

Mi implementación de largees simplemente un tipo copiable / móvil que contiene un gran recurso que advierte sobre la copia / movimiento.

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

Editar

Al usar la reserva, no hay copia ni movimiento. Solo large::large(std::size_t)se invoca el constructor. El verdadero lugar.

reconocer
fuente
1
No es una inicialización agregada, estás llamando al constructor que toma un std::initializer_list.
Súper
Los constructores de std :: vector son un lío confuso en mi humilde opinión, y si fuera usted, realmente evitaría entrar en él si no tengo que hacerlo. Además, si conoce los contenidos de sus vectores de antemano (lo que parece ser el caso), considere un std::array.
Einpoklum
1
Una operator=que destruye la fuente es bastante inusual y puede causar problemas inesperados.
Alain
1
init_emplace_vectorpodría mejorarse convec.reserve(sizeof...(Args))
Indiana Kernick
1
@alain operator=no destruye la fuente. Es el idioma de intercambio de copias.
Reconn

Respuestas:

11

¿Puedo agregar-inicializar std :: vector ...

No. std::vectorno es un agregado, por lo que no se puede inicializar de forma agregada.

Puede referirse a la inicialización de la lista, en cuyo caso:

¿Puedo [list-initialise] std :: vector con reenvío perfecto de los elementos?

No. List-initialialisation usa el std::initializer_listconstructor y std::initializer_listcopia sus argumentos.

Su init_emplace_vectorparece ser una solución decente, aunque se puede mejorar reservando la memoria antes de emplazar los elementos.

eerorika
fuente
1
Gracias por corregirme. Editado Buen punto con reserva.
Reconn