Posso listar a inicialização do std :: vector com o encaminhamento perfeito dos elementos?

14

Percebi que a initalização de lista agregada do std :: vector realiza a inicialização da cópia quando a movimentação é mais aplicável. Ao mesmo tempo, vários emplace_backs fazem o que eu quero.

Eu só poderia encontrar esta solução imperfeita de escrever uma função de modelo init_emplace_vector. É ideal apenas para construtores de valor único não 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;
}

Questão

Eu realmente preciso usar emplace_back para inicializar o std :: vector da maneira mais eficiente possível?

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

Resultado

A classe largeproduz informações sobre cópias / movimentos (implementação abaixo) e, portanto, a saída do meu programa é:

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

Implementação de classe grande

Minha implementação largeé simplesmente um tipo copiável / móvel, contendo um grande recurso que avisa sobre copiar / mover.

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

Usando reserva, não há cópia ou movimentação. Somente o large::large(std::size_t)construtor é chamado. Verdadeiro lugar.

reconectar
fonte
11
Não é inicialização agregada, você está chamando o construtor que leva a std::initializer_list.
super
Os construtores de std :: vector são uma bagunça confusa, IMHO, e se eu fosse você, evitaria realmente entrar nela se não for absolutamente necessário. Além disso, se você conhece o conteúdo dos vetores com antecedência (o que parece ser o caso) - considere um std::array.
einpoklum 9/01
11
Um operator=que destrói a fonte é bastante incomum e pode causar problemas inesperados.
alain
11
init_emplace_vectorpoderia ser melhorado comvec.reserve(sizeof...(Args))
Indiana Kernick
11
@alain operator=não destrói a fonte. É o idioma de troca de cópias.
reconectar

Respostas:

11

Posso agregar inicializar std :: vector ...

No. std::vectornão é um agregado; portanto, não pode ser inicializado por agregado.

Você pode significar a inicialização da lista, nesse caso:

Posso [listar-inicializar] std :: vector com encaminhamento perfeito dos elementos?

Não. A inicialização da lista usa o std::initializer_listconstrutor e std::initializer_listcopia seus argumentos.

Sua init_emplace_vectorparece ser uma solução decente, embora possa ser aprimorada reservando a memória antes de colocar os elementos.

eerorika
fonte
11
Obrigado por me corrigir. Editado. Bom ponto com reserva.
reconectar