Eu estou usando uma biblioteca externa que em algum momento me dá um ponteiro bruto para uma matriz de números inteiros e um tamanho.
Agora eu gostaria de usar std::vector
para acessar e modificar esses valores no lugar, em vez de acessá-los com ponteiros brutos.
Aqui está um exemplo articifial que explica o ponto:
size_t size = 0;
int * data = get_data_from_library(size); // raw data from library {5,3,2,1,4}, size gets filled in
std::vector<int> v = ????; // pseudo vector to be used to access the raw data
std::sort(v.begin(), v.end()); // sort raw data in place
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n"; // display sorted raw data
}
Saída esperada:
1
2
3
4
5
O motivo é que preciso aplicar algoritmos <algorithm>
(classificação, troca de elementos etc.) nesses dados.
Por outro lado alterando o tamanho desse vector nunca iria ser mudado, assim push_back
, erase
, insert
não são obrigados a trabalhar nisso vetor.
Eu poderia construir um vetor com base nos dados da biblioteca, usar modificar esse vetor e copiar os dados de volta para a biblioteca, mas seriam duas cópias completas que eu gostaria de evitar, pois o conjunto de dados pode ser muito grande.
std::vector_view
, não é?std::vector
funciona.sort(arrayPointer, arrayPointer + elementCount);
.Respostas:
O problema é que
std::vector
é necessário fazer uma cópia dos elementos da matriz com a qual você a inicializa, pois possui a propriedade dos objetos que ela contém.Para evitar isso, você pode usar um objeto de fatia para uma matriz (ou seja, semelhante ao que
std::string_view
éstd::string
). Você pode escrever sua própriaarray_view
implementação de modelo de classe cujas instâncias são construídas levando um ponteiro bruto para o primeiro elemento de uma matriz e o comprimento da matriz:array_view
não armazena uma matriz; apenas mantém um ponteiro para o início da matriz e o comprimento dessa matriz. Portanto, osarray_view
objetos são baratos para construir e copiar.Desde
array_view
fornece osbegin()
eend()
membro funções, você pode usar os algoritmos da biblioteca padrão (por exemplo,std::sort
,std::find
,std::lower_bound
, etc.) sobre ele:Resultado:
Use
std::span
(ougsl::span
)A implementação acima expõe o conceito por trás dos objetos de fatia . No entanto, desde C ++ 20, você pode usar diretamente
std::span
. Em qualquer caso, você pode usargsl::span
desde o C ++ 14.fonte
C ++ 20's
std::span
Se você puder usar o C ++ 20, poderá usar
std::span
um par de comprimento de ponteiro que permita ao usuário visualizar uma sequência contígua de elementos. É uma espécie de astd::string_view
, e embora ambas as visualizações sejamstd::span
estd::string_view
não proprietárias,std::string_view
é uma visualização somente leitura.Dos documentos:
Portanto, o seguinte funcionaria:
Confira ao vivo
Como
std::span
é basicamente um par de comprimento de ponteiro, você também pode usar da seguinte maneira:Nota: Nem todos os compiladores suportam
std::span
. Verifique o suporte do compilador aqui .ATUALIZAR
Se você não conseguir usar o C ++ 20, poderá usar
gsl::span
a versão básica dos padrões do C ++std::span
.Solução C ++ 11
Se você está limitado ao padrão C ++ 11, pode tentar implementar sua própria
span
classe simples :Confira a versão C ++ 11 ao vivo
fonte
gsl::span
C ++ 14 e superior se o seu compilador não implementarstd::span
Como a biblioteca de algoritmos trabalha com iteradores, você pode manter a matriz.
Para ponteiros e comprimento conhecido da matriz
Aqui você pode usar ponteiros brutos como iteradores. Eles suportam todas as operações que um iterador suporta (incremento, comparação para igualdade, valor de, etc ...):
data
aponta para o membro da matriz dirst como um iterador retornado porbegin()
edata + size
aponta para o elemento após o último elemento da matriz como um iterador retornado porend()
.Para matrizes
Aqui você pode usar
std::begin()
estd::end()
Mas lembre-se de que isso só funciona, se
data
não se deteriorar para um ponteiro, porque as informações de comprimento desaparecem.fonte
Você pode obter iteradores em matrizes brutas e usá-los em algoritmos:
Se você estiver trabalhando com ponteiros não processados (ptr + tamanho), poderá usar a seguinte técnica:
UPD: No entanto, o exemplo acima é de design incorreto. A biblioteca nos retorna um ponteiro bruto e não sabemos onde o buffer subjacente está alocado e quem deve liberá-lo.
Normalmente, o chamador fornece um buffer para a função preencher os dados. Nesse caso, podemos pré-alocar o vetor e usar seu buffer subjacente:
Ao usar o C ++ 11 ou superior, podemos fazer com que get_data_from_library () retorne um vetor. Graças às operações de movimentação, não haverá cópia de memória.
fonte
auto begin = data;
auto end = data + size;
get_data_from_library()
são alocados? Talvez não devamos mudar isso. Se precisarmos passar um buffer para a biblioteca, podemos alocar vetor e passarv.data()
Você não pode fazer isso com um
std::vector
sem fazer uma cópia.std::vector
possui o ponteiro que possui sob o capô e aloca espaço através do alocador que é fornecido.Se você tiver acesso a um compilador que suporte C ++ 20, poderá usar std :: span, que foi criado exatamente para esse fim. Ele agrupa um ponteiro e tamanho em um "contêiner" que possui a interface do contêiner C ++.
Caso contrário, você pode usar o gsl :: span, do qual a versão padrão foi baseada.
Se você não quiser importar outra biblioteca, poderá implementá-la trivialmente, dependendo de todas as funcionalidades que desejar.
fonte
Você não pode. Não
std::vector
é para isso que serve.std::vector
gerencia seu próprio buffer, que é sempre adquirido de um alocador. Ele nunca se apropria de outro buffer (exceto de outro vetor do mesmo tipo).Por outro lado, você também não precisa, porque ...
Esses algoritmos funcionam em iteradores. Um ponteiro é um iterador para uma matriz. Você não precisa de um vetor:
Diferentemente dos modelos de função
<algorithm>
, algumas ferramentas, como intervalos parastd::begin
/ ,std::end
e C ++ 20, não funcionam apenas com um par de iteradores, enquanto trabalham com contêineres, como vetores. É possível criar uma classe de wrapper para o tamanho do iterador + que se comporta como um intervalo e funciona com essas ferramentas. C ++ 20 irá introduzir tais invólucro para a biblioteca padrão:std::span
.fonte
Além da outra boa sugestão sobre a
std::span
entrada em c ++ 20 egsl:span
, incluindo sua própriaspan
classe (leve) até então, já é fácil o suficiente (sinta-se à vontade para copiar):De nota especial também é a biblioteca de faixa de impulso boost-range, se você estiver interessado no conceito de faixa mais genérico: https://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/reference /utilities/iterator_range.html .
Os conceitos de intervalo também chegarão em c ++ 20
fonte
using value_type = std::remove_cv_t<T>;
?span(T* first_, size_t length) : first(first), length(length) {};
. Eu editei sua resposta.using value_type = std::remove_cv_t<T>;
é necessário principalmente se usado com a programação de modelos (para obter o tipo de valor de um 'intervalo'). Se você quiser apenas usar os iteradores, pode pular / remover isso.Na verdade, você quase poderia usar
std::vector
isso abusando da funcionalidade do alocador personalizado para retornar um ponteiro para a memória que deseja exibir. Isso não seria garantido pelo padrão para funcionar (preenchimento, alinhamento, inicialização dos valores retornados; você teria que se esforçar ao atribuir o tamanho inicial, e para os não-primitivos, você também precisaria hackear seus construtores ), mas na prática eu esperaria que ele desse ajustes suficientes.Nunca, jamais, faça isso. É feio, surpreendente, hacky e desnecessário. Os algoritmos da biblioteca padrão já foram projetados para funcionar tanto com matrizes brutas quanto com vetores. Veja as outras respostas para detalhes disso.
fonte
vector
construtores que usam uma referência personalizada do Alocador como um construtor arg (não apenas como um parâmetro de modelo). Eu acho que você precisaria de um objeto de alocador que tivesse o valor do ponteiro de tempo de execução, não como um parâmetro de modelo, caso contrário, ele só funcionaria para endereços constexpr. Você precisaria ter cuidado para não permitir quevector
objetos de construção padrão.resize()
substituíssem os dados existentes; a incompatibilidade entre um contêiner proprietário como vetor versus período não-proprietário é enorme se você começar a usar .push_back etc.construct
método que seria necessário ... Não consigo pensar em quais casos de uso não hacky exigiriam isso sobre a colocação de novo.resize()
antes de passar uma referência a algo que deseja usá-lo como uma saída pura (por exemplo, uma chamada de sistema de leitura). Na prática, os compiladores geralmente não otimizam esse memset ou o que seja. Ou, se você tivesse um alocador que usa calloc para obter memória pré-zerada, também poderia evitar sujá-la da maneira estúpidastd::vector<int>
por padrão ao construir objetos padrão que possuem um padrão de bits zero. Veja as notas em en.cppreference.com/w/cpp/container/vector/vectorComo outros já apontaram,
std::vector
deve possuir a memória subjacente (além de mexer com um alocador personalizado) para que não possa ser usado.Outros também recomendaram a extensão do c ++ 20, no entanto, obviamente, isso requer c ++ 20.
Eu recomendaria o período span-lite . Para citar sua legenda:
Ele fornece uma visão não proprietária e mutável (como você pode alterar elementos e sua ordem, mas não inseri-los) e, como diz a citação, não possui dependências e funciona na maioria dos compiladores.
Seu exemplo:
Impressões
Isso também tem uma vantagem adicional: se um dia você mudar para c ++ 20, poderá substituir isso
nonstd::span
porstd::span
.fonte
Você pode usar um
std::reference_wrapper
disponível desde o C ++ 11:fonte
std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));
definitivamente preenche osdest_vector
valores obtidossrc_table
(IOW para os quais os dados são copiadosdest_vector
), então não recebi seu comentário. Você poderia explicar?