Ik'gebruik een externe bibliotheek die me op een bepaald punt een ruwe pointer geeft naar een array van gehele getallen en een grootte.
Nu wil ik std::vector
gebruiken om deze waarden ter plaatse te benaderen en te wijzigen, in plaats van ze te benaderen met ruwe pointers.
Hier is een voorbeeld dat het punt uitlegt:
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
}
Verwachte output:
1
2
3
4
5
De reden is dat ik algoritmes van <algorithm>
(sorteren, elementen verwisselen enz.) op die gegevens moet toepassen.
Aan de andere kant zou de grootte van die vector nooit veranderd worden, dus push_back
, erase
, insert
zijn niet nodig om op die vector te werken.
Ik zou een vector kunnen construeren op basis van de gegevens uit de bibliotheek, die vector kunnen wijzigen en de gegevens terug naar de bibliotheek kunnen kopiëren, maar dat zouden twee volledige kopieën zijn die ik zou willen vermijden omdat de dataset erg groot zou kunnen zijn.
std::span
Als u C++20 kunt gebruiken, kunt u std::span
gebruiken, wat een aanwijzer is - lengtepaar dat de gebruiker een beeld geeft van een aaneengesloten reeks elementen. Het is een soort van std::string_view
, en terwijl zowel std::span
als std::string_view
niet-eigenarenaanzichten zijn, is std::string_view
een alleen-lezen-aanzicht.
Van de docs:
De class template span beschrijft een object dat kan verwijzen naar een aaneengesloten sequentie van objecten met het eerste element van de sequentie op positie nul. Een overspanning kan een statische omvang hebben, waarbij indien het aantal elementen in de reeks bekend is en gecodeerd in het type, of een dynamische omvang.
Dus het volgende zou werken:
#inclusief <span>
#inclusief <iostream>
#inclusief <algoritme>
int main() {
int data[] = { 5, 3, 2, 1, 4 };
std::span<int> s{data, 5};
std::sort(s.begin(), s.end());
voor (auto const i : s) {
Cout << i << "\n";
}
0 terugbrengen;
}
Bekijk het [live][1]
Aangezien std::span
in principe pointer - lengte paar is, kunt u ook op een volgende manier gebruiken:
size_t size = 0;
int *data = get_data_from_library(size);
std::span<int> s{data, size};
Note: Niet alle compilers ondersteunen std::span
. Controleer de ondersteuning van de compilers hier.
UPDATE
Als u niet in staat bent om C++20 te gebruiken, kunt u gsl::span
gebruiken, wat in principe de basisversie is van de C++ standaard std::span
.
Als u zich beperkt tot de C++11 standaard, kunt u proberen uw eigen eenvoudige span
klasse te implementeren:
sjabloon<typenaam T>
klasseoverspanning {
T* ptr_;
std::size_t len_;
publiek:
spanwijdte (T* ptr, std::size_t len) noexcept
: ptr_{ptr}, len_{len}
{}
T& operator[](int i) noexcept {
terug *ptr_[i];
}
T const& operator[](int i) const noexcept {
terug *ptr_[i];
}
std::size_t size() const noexcept {
terugsturen len_;
}
T* begin() noexcept {
terugsturen ptr_;
}
T* end() noexcept {
retour ptr_ + len_;
}
};
Bekijk de C++11 versie live.
https://godbolt.org/#g:!((g:!((g:!) ((h:codeEditor,i:(lettertypeSchaal:14,j:1,lang:c%2B%2B,selectie:(endColumn:2,endLineNumber:16,positionColumn:1,positionLineNumber:1,selectionStartColumn:2,selectionStartLineNumber:16,startColumn:1,startLineNumber:1),bron: '%23include+%3Cspan%3E%0A%23include+%3Ciostream%3E%0A%23include+%3Calgorithm%3E%0A%0Aint+main()+%7B%0A++++int+data%5B%5D+%3D+%7B+5,+3,+2,+1,+4+%7D%3B%0A++++std: :span%3Cint%3E+s%7Bdata,+5%7D%3B%0A%0A++++std::sort(s. begin(),+s.end())%3B%0A%0A++++for+(auto+i+:+s)+%7B%0A++++++++std::cout+%3C%3C+i+%3C%3C+%22%5Cn%22%3B%0A++++%7D%0A%0A++++return+0%3B%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:49. 85494633014216,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((g:! ((h:compiler,i:(compiler:gsnapshot,filters:(b:'0',binair:'1',commentaarAlleen:'0',demangle:'0',richtlijnen:'0',uitvoeren:'0',intel:'0',bibliotheekCode:'1',trim:'1'),lettertypeSchaal: 14,j:2,lang:c%2B%2B,libs:! (),opties:'-std%3Dc%2B%2B2a',selectie:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber: 1,startKolom:1,startLineNumber:1),bron:1),l:'5',n:'0',o:'x86-64+gcc+(kofferbak)+(editor+%231,+Compiler+%232)+C%2B%2B',t:'0'),k:50. 145053669857845,l:'4',m:50,n:'0',o:'',s:0,t:'0'),(g:! ((h:output,i:(compiler:2,editor:1,fontScale:14,wrap:'1'),l:'5',n:'0',o:'%232+met+x86-64+gcc+(stam)',t:'0')),header:(),l:'4',m:50,n:'0',o:',s:0,t:'0')),k:50. 145053669857845,l:'3',n:'0',o:'',t:'0')),l:'2',n:'0',o:'',t:'0')),version:4
Het probleem is dat std::vector
een kopie moet maken van de elementen uit de array waarmee je het initialiseert, omdat het de eigendom heeft van de objecten die het bevat.
Om dit te voorkomen, kunt u een slice object gebruiken voor een array (d.w.z., vergelijkbaar met wat std::string_view
is voor std::string
). Je zou je eigen array_view
klasse sjabloonimplementatie kunnen schrijven waarvan de instanties worden geconstrueerd door een ruwe pointer naar het eerste element van een array en de lengte van de array te nemen:
#include <cstdint>
template<typename T>
class array_view {
T* ptr_;
std::size_t len_;
public:
array_view(T* ptr, std::size_t len) noexcept: ptr_{ptr}, len_{len} {}
T& operator[](int i) noexcept { return ptr_[i]; }
T const& operator[](int i) const noexcept { return ptr_[i]; }
auto size() const noexcept { return len_; }
auto begin() noexcept { return ptr_; }
auto end() noexcept { return ptr_ + len_; }
};
Array_viewslaat geen array op; het houdt alleen een pointer vast naar het begin van de array en de lengte van die array. Daarom zijn
array_view` objecten goedkoop om te construeren en te kopiëren.
Aangezien array_view
de begin()
en einde()
lidfuncties biedt, kunt u de standaard bibliotheekalgoritmes (b.v. std::sorteren
, std::vinden
, std::lager_gebonden
, etc.) erop gebruiken:
#define LEN 5
auto main() -> int {
int arr[LEN] = {4, 5, 1, 2, 3};
array_view<int> av(arr, LEN);
std::sort(av.begin(), av.end());
for (auto const& val: av)
std::cout << val << ' ';
std::cout << '\n';
}
Uitgang:
1 2 3 4 5
std::span
(of gsl::span
)De bovenstaande implementatie legt het concept achter slice objects bloot. Echter, sinds C++20 kunt u direct gebruik maken van std::span
. In ieder geval kunt u sinds C++14 gebruik maken van [gsl::span
][2].
Aangezien de algoritme-bibliotheek met iterators werkt, kun je de array behouden.
Hier kun je raw pointers als iterators gebruiken. Ze ondersteunen alle opertaties die een iterator ondersteunt (increment, vergelijking voor gelijkheid, waarde van, etc...):
#include <iostream>
#include <algorithm>
int *get_data_from_library(int &size) {
static int data[] = {5,3,2,1,4};
size = 5;
geef data terug;
}
int main()
{
int size;
int *data = get_data_from_library(size);
std::sort(data, data + size);
for (int i = 0; i < size; i++)
{
std::cout << data[i] << "\n";
}
}
data
wijst naar het eerste array lid als een iterator geretourneerd door begin()
en data + size
wijst naar het element na het laatste element van de array als een iterator geretourneerd door end()
.
Hier kun je std::begin()
en std::end()
gebruiken
#include <iostream>
#include <algorithm>
int main()
{
int data[] = {5,3,2,1,4}; // ruwe gegevens uit bibliotheek
std::sort(std::begin(data), std::end(data)); // ruwe gegevens op hun plaats sorteren
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n" // gesorteerde ruwe gegevens weergeven
}
}
Maar bedenk wel dat dit alleen werkt, als data
niet vervalt naar een pointer, want dan ontbreekt lengte-informatie.
Je kunt iterators krijgen op ruwe arrays en ze gebruiken in algoritmes:
int data[] = {5,3,2,1,4};
std::sort(std::begin(data), std::end(data));
for (auto i : data) {
std::cout << i << std::endl;
}
Als je werkt met raw pointers (ptr + size), dan kun je de volgende techniek gebruiken:
size_t size = 0;
int * data = get_data_from_library(size);
auto b = data;
auto e = b + size;
std::sort(b, e);
for (auto it = b; it != e; ++it) {
cout << *it << endl;
}
UPD: Het bovenstaande voorbeeld is echter slecht ontworpen. De library geeft ons een ruwe pointer terug en we weten niet waar de onderliggende buffer is gealloceerd en wie hem moet bevrijden.
Gewoonlijk levert de aanroeper een buffer voor de functie om de gegevens te vullen. In dat geval kunnen we de vector prealloceren en de onderliggende buffer gebruiken:
std::vector<int> v;
v.resize(256); // wijs een buffer toe voor 256 gehele getallen
size_t size = get_data_from_library(v.data(), v.size());
// inkrimpen tot werkelijke data. Merk op dat hier geen geheugen realocatie of kopie wordt gedaan.
v.resize(size);
std::sort(v.begin(), v.einde());
for (auto i : v) {
cout << i << endl;
}
Als we C++11 of hoger gebruiken, kunnen we zelfs get_data_from_library() een vector laten teruggeven. Dankzij move operaties zal er geen geheugen gekopieerd worden.
Nu wil ik graag std::vector gebruiken om toegang te krijgen tot deze waarden en ze aan te passen.
Dat kan niet. Dat is niet waar std::vector
voor is. std::vector
beheert zijn eigen buffer, die altijd wordt verkregen van een toewijzer. Hij neemt nooit een andere buffer in bezit (behalve van een andere vector van hetzelfde type).
Aan de andere kant hoef je dat ook niet te doen, want ...
De reden is dat ik algoritmes van
(sorteren, swapen van elementen etc.) op die gegevens moet toepassen.
Die algoritmes werken op iteratoren. Een pointer is een iterator naar een array. Je hebt geen vector nodig:
std::sort(data, data + size);
In tegenstelling tot functiesjablonen in <algoritme>
, werken sommige tools zoals range-for,std::begin
/std::end
en C++20 bereiken echter niet met slechts een paar iteratoren, terwijl ze wel werken met containers zoals vectoren. Het is mogelijk om een wikkelklasse voor iterator + maat te maken die zich gedraagt als een bereik, en werkt met deze gereedschappen. C++20 zal een dergelijke wrapper introduceren in de standaard bibliotheek: std::span
.
Je kunt dit niet doen met een std::vector
zonder een kopie te maken. std::vector
is eigenaar van de pointer die het onder de motorkap heeft en wijst ruimte toe via de allocator die wordt meegeleverd.
Als u toegang heeft tot een compiler die C++20 ondersteunt, kunt u gebruik maken van std::span, die precies voor dit doel is gebouwd. Het verpakt een pointer en grootte in een "container" die de C++ container interface heeft.
Zo niet, dan kunt u gsl::span gebruiken, waar de standaard versie op is gebaseerd.
Als je geen andere bibliotheek wilt importeren, kun je dit triviaal zelf implementeren, afhankelijk van welke functionaliteit je wilt hebben.
Eigenlijk zou je *kunnen gebruiken std::vector
voor dit, door misbruik te maken van de aangepaste allocator-functionaliteit om een aanwijzer terug te geven aan het geheugen dat je wilt bekijken. Dat zou niet gegarandeerd worden door de standaard om te werken (opvulling, uitlijning, initialisatie van de geretourneerde waarden; je zou pijn moeten doen bij het toewijzen van de initiële grootte, en voor niet-primitieven zou je ook je constructeurs moeten hacken), maar in de praktijk zou ik verwachten dat het genoeg tweaks zou geven.
Doe dat nooit. Het is lelijk, verrassend, hacky en onnodig. De algoritmes van de standaardbibliotheek zijn already ontworpen om zowel met ruwe arrays als met vectoren te werken. Zie de andere antwoorden voor meer informatie.
Naast de andere goede suggestie over std::span
komen in [tag:c++20] en gsl:span
, inclusief uw eigen (lichtgewicht) span
klasse tot die tijd is al makkelijk genoeg (voel je vrij om te kopiëren):
template<class T>
struct span {
T* first;
size_t length;
span(T* first_, size_t length_) : first(first_), length(length_) {};
using value_type = std::remove_cv_t<T>;//primarily needed if used with templates
bool empty() const { return length == 0; }
auto begin() const { return first; }
auto end() const { return first + length; }
};
static_assert(_MSVC_LANG <= 201703L, "remember to switch to std::span");
Bijzonder is ook de boost-bibliotheek [tag:boost-range] als u geïnteresseerd bent in het meer generieke bereikconcept: https://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/reference/utilities/iterator_range.html.
Bereikconcepten komen ook aan in [tag:c++20].
U kunt een [std::referentie_wrapper
][1] gebruiken die sinds C++11 beschikbaar is:
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
int main()
{
int src_table[] = {5, 4, 3, 2, 1, 0};
std::vector< std::reference_wrapper< int > > dest_vector;
std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));
// if you don't have the array defined just a pointer and size then:
// std::copy(src_table_ptr, src_table_ptr + size, std::back_inserter(dest_vector));
std::sort(std::begin(dest_vector), std::end(dest_vector));
std::for_each(std::begin(src_table), std::end(src_table), [](int x) { std::cout << x << '\n'; });
std::for_each(std::begin(dest_vector), std::end(dest_vector), [](int x) { std::cout << x << '\n'; });
}
https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper.
Zoals anderen al hebben aangegeven, moet std::vector
het onderliggende geheugen bezitten (kortom: knoeien met een aangepaste allocator) en kan dus niet worden gebruikt.
Anderen hebben ook c++20's span aanbevolen, maar dat vereist natuurlijk c++20.
Ik zou de span-lite span aanbevelen. Om de ondertitel aan te halen:
span-lite - Een C++20-achtige span voor C++98, C++11 en later in een enkel-bestandenbibliotheek.
Het biedt een niet-eigenaar en mutabele weergave (zoals in u kunt muteren elementen en hun volgorde, maar niet invoegen) en zoals de offerte zegt heeft geen afhankelijkheden en werkt op de meeste compilers.
Uw voorbeeld:
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <nonstd/span.hpp>
static int data[] = {5, 1, 2, 4, 3};
// For example
int* get_data_from_library()
{
return data;
}
int main ()
{
const std::size_t size = 5;
nonstd::span<int> v{get_data_from_library(), size};
std::sort(v.begin(), v.end());
for (auto i = 0UL; i < v.size(); ++i)
{
std::cout << v[i] << "\n";
}
}
Afdrukken
1
2
3
4
5
Dit heeft ook het voordeel dat als je op een dag overschakelt naar c++20, je deze nonstd::span
zou moeten kunnen vervangen door std::span
.