Como inicializar std :: vector da matriz C-style?

174

Qual é a maneira mais barata de inicializar a std::vectorpartir de uma matriz no estilo C?

Exemplo: Na classe a seguir, eu tenho um vector, mas devido a restrições externas, os dados serão passados ​​como uma matriz no estilo C:

class Foo {
  std::vector<double> w_;
public:
  void set_data(double* w, int len){
   // how to cheaply initialize the std::vector?
}

Obviamente, eu posso ligar w_.resize()e depois fazer um loop sobre os elementos, ou ligar std::copy(). Existem métodos melhores?

Frank
fonte
11
O ponto crucial do problema é que não há como o vetor saber se o mesmo alocador foi usado para criar sua matriz no estilo C. Como tal, o vetor deve alocar memória usando seu próprio alocador. Caso contrário, ele poderia simplesmente trocar a matriz subjacente e substituí-la por sua matriz.
01

Respostas:

233

Não esqueça que você pode tratar os ponteiros como iteradores:

w_.assign(w, w + len);
Pavel Minaev
fonte
3
É um problema de qualidade de implementação. Como os iteradores têm tags que especificam suas categorias, uma implementação de assigné certamente livre para usá-las para otimizar; pelo menos no VC ++, ele realmente faz exatamente isso.
Pavel Minaev 14/03/10
38
A solução rápida pode ser std :: vector <double> w_ (w, w + len);
211313 jamk
1
Isso copia elementos para um armazenamento recém-criado para 'w_'; 'w_.data' não apontará para 'w'. Você ainda precisa desalocar 'w'. Não há transferência de propriedade
Indy9000
1
Se for um elemento após o final, tudo ficará bem (assim como v.end()um iterador apontando um após o final com vetor em um caso semelhante). Se você receber uma afirmação, algo está errado em outro lugar.
Pavel Minaev
1
Rapidamente, isso desalocará a memória da matriz quando o vetor ficar fora do escopo?
Adrian Koch
41

Você usa a palavra inicializar, portanto, não está claro se essa é uma atribuição única ou pode ocorrer várias vezes.

Se você precisar apenas de uma inicialização única, poderá colocá-la no construtor e usar o construtor de vetor de dois iteradores:

Foo::Foo(double* w, int len) : w_(w, w + len) { }

Caso contrário, use assign como sugerido anteriormente:

void set_data(double* w, int len)
{
    w_.assign(w, w + len);
}
Mark B
fonte
1
No meu caso, a tarefa acontecerá repetidamente.
31710 Frank
12

Você pode 'aprender' o tamanho da matriz automaticamente:

template<typename T, size_t N>
void set_data(const T (&w)[N]){
    w_.assign(w, w+N);
}

Felizmente, você pode alterar a interface para set_data como acima. Ele ainda aceita uma matriz no estilo C como seu primeiro argumento. Acontece apenas por referência.


Como funciona

[Atualização: Veja aqui para uma discussão mais abrangente sobre como aprender o tamanho]

Aqui está uma solução mais geral:

template<typename T, size_t N>
void copy_from_array(vector<T> &target_vector, const T (&source_array)[N]) {
    target_vector.assign(source_array, source_array+N);
}

Isso funciona porque a matriz está sendo transmitida como uma referência a uma matriz. No C / C ++, você não pode passar uma matriz como uma função; em vez disso, ela se deteriorará para um ponteiro e você perderá o tamanho. Mas em C ++, você pode passar uma referência para a matriz.

Passar uma matriz por referência requer que os tipos correspondam exatamente. O tamanho de uma matriz faz parte de seu tipo. Isso significa que podemos usar o parâmetro N do modelo para aprender o tamanho para nós.

Pode ser ainda mais simples ter essa função que retorna um vetor. Com as otimizações apropriadas do compilador em vigor, isso deve ser mais rápido do que parece.

template<typename T, size_t N>
vector<T> convert_array_to_vector(const T (&source_array)[N]) {
    return vector<T>(source_array, source_array+N);
}
Aaron McDaid
fonte
1
Na última amostra, return { begin(source_array), end(source_array) };também é possível #
MM
12

Bem, Pavel estava perto, mas há ainda uma solução mais simples e elegante para inicializar um contêiner seqüencial a partir de uma matriz de estilo CA.

No seu caso:

w_ (array, std::end(array))
  • array nos dará um ponteiro para o início do array (não pegou o nome),
  • std :: end (array) nos fornecerá um iterador para o final do array.
Mugurel
fonte
1
O que inclui / versão do C ++ isso requer?
Vlad
1
Este é um dos construtores do std :: vector a partir de pelo menos c ++ 98 em diante .... É chamado de 'construtor de intervalo'. cplusplus.com/reference/vector/vector/vector Experimente.
Mugurel
1
A versão mais independente é: w_ (std :: begin (array), std :: end (array)); (No futuro, você pode alterar uma matriz C para um contêiner C ++).
Andrew Romanov
13
Lembre-se, isso só funciona se você tiver uma arraymatriz real (o que geralmente significa que você está copiando de uma matriz global ou local (declarada na função atual)). No caso do OP, ele está recebendo um ponteiro e um comprimento, e como não está modelado no comprimento, eles não podem mudar para receber um ponteiro para uma matriz de tamanho ou algo assim, portanto std::endnão funcionará.
ShadowRanger
2
vectornão sobrecarrega operator(), portanto, isso não será compilado. std::endser chamado em um ponteiro também não serve para nada (a pergunta pede para atribuir um vetor de um ponteiro e uma variável de comprimento separada). Ele iria melhorar a sua resposta para mostrar mais contexto sobre o que você está tentando sugerir
MM
2

A resposta genérica rápida:

std::vector<double> vec(carray,carray+carray_size); 

ou pergunta específica:

std::vector<double> w_(w,w+len); 

com base no exposto acima : não esqueça que você pode tratar os ponteiros como iteradores

EUSOU brasileiro
fonte
0

std::vector<double>::assigné o caminho a seguir, porque é pouco código . Mas como isso funciona, na verdade? Não redimensiona e copia? Na implementação do STL pela Microsoft, estou usando exatamente o mesmo.

Receio que não haja uma maneira mais rápida de implementar (re) inicializar o seu std::vector.

Janusz Lenar
fonte
e se os dados fossem compartilhados entre o vetor e uma matriz? Precisamos copiar alguma coisa neste caso?
Vlad
isso é uma resposta ou uma pergunta? o que isso traz para as respostas já existentes?
Jean-François Fabre
@ Jean-FrançoisFabre e o que seu comentário traz? ;) verdade, é uma resposta ruim dada há muito tempo.
Janusz Lenar