Gebruik std::vector als zicht op ruw geheugen

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.

Commentaar op de vraag (11)

C++20's 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 
#inclusief 

int main() {
    int data[] = { 5, 3, 2, 1, 4 };
    std::span s{data, 5};

    std::sort(s.begin(), s.end());

    voor (auto const i : s) {
        Cout 
Commentaren (2)
Oplossing

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 

template
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 zijnarray_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 av(arr, LEN);

   std::sort(av.begin(), av.end());

   for (auto const& val: av)
      std::cout 
Commentaren (2)

Aangezien de algoritme-bibliotheek met iterators werkt, kun je de array behouden.

Voor pointers en bekende array lengte

Hier kun je raw pointers als iterators gebruiken. Ze ondersteunen alle opertaties die een iterator ondersteunt (increment, vergelijking voor gelijkheid, waarde van, etc...):


#include 
#include 

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 
Commentaren (1)

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 
Commentaren (4)

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 `, werken sommige tools zoals range-for,std::begin/std::enden 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`.

Commentaren (0)

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.

Commentaren (0)

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.

Commentaren (3)

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
struct span {
    T* first;
    size_t length;
    span(T* first_, size_t length_) : first(first_), length(length_) {};
    using value_type = std::remove_cv_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 
Commentaren (6)

U kunt een [std::referentie_wrapper][1] gebruiken die sinds C++11 beschikbaar is:


#include 
#include 
#include 
#include 

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 
Commentaren (10)

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][1] 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 
#include 
#include 

#include 

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 v{get_data_from_library(), size};

  std::sort(v.begin(), v.end());

  for (auto i = 0UL; i < v.size(); ++i)
  {
    std::cout 
Commentaren (0)