Jak dokonujesz „realokacji” w C ++?

85

Jak mogę reallocw C ++? Wydaje się, że brakuje tego języka - jest newi deleteale nie ma resize!

Potrzebuję tego, ponieważ ponieważ mój program odczytuje więcej danych, muszę ponownie przydzielić bufor, aby go przechowywać. Myślę, że deletestary wskaźnik i newnowy, większy, nie są właściwą opcją.

bodacydo
źródło
8
Stroustrup odpowiedział na to dawno temu, zobacz: www2.research.att.com/~bs/bs_faq2.html#renew (To dobry początek, jeśli jesteś nowy w C ++ i często zadawanych pytaniach dotyczących C ++
Cline'a
8
Odpowiedź, do której odwołuje się @dirkgently, jest teraz pod adresem: stroustrup.com/bs_faq2.html#renew - a FAQ Cline'a jest teraz częścią super FAQ: isocpp.org/faq
maxschlepzig

Odpowiedzi:

54

Użyj :: std :: vector!

Type* t = (Type*)malloc(sizeof(Type)*n) 
memset(t, 0, sizeof(Type)*m)

staje się

::std::vector<Type> t(n, 0);

Następnie

t = (Type*)realloc(t, sizeof(Type) * n2);

staje się

t.resize(n2);

Jeśli chcesz przekazać wskaźnik do funkcji, zamiast

Foo(t)

posługiwać się

Foo(&t[0])

Jest to absolutnie poprawny kod w C ++, ponieważ wektor jest inteligentną tablicą C.

f0b0s
źródło
1
Czy wiersz memset powinien być memset (t, 0, sizeof (T) * n) ;? n zamiast m?
Raphael Mayer
1
@anthom yes. naprawdę powinno byćType* t = static_cast<Type*>(malloc(n * sizeof *t));
Ryan Haining
2
W C ++ 11 można by teraz użyć t.data()zamiast&t[0]
knedlsepp
1
Jak możesz to usunąć?
a3mlord
@ a3mlord: Co masz na myśli? Niech to wypadnie poza zakres i zniknie.
Wyścigi lekkości na orbicie
51

Prawdopodobnie właściwą opcją jest użycie kontenera, który wykona pracę za Ciebie, na przykład std::vector.

newi deletenie mogą zmienić rozmiaru, ponieważ przydzielają wystarczającą ilość pamięci, aby pomieścić obiekt danego typu. Rozmiar danego typu nigdy się nie zmieni. Są new[]i delete[]ale prawie nigdy nie ma powodu, aby z nich korzystać.

To, co reallocrobi w C, prawdopodobnie będzie po prostu malloc, memcpyi freetak czy inaczej, chociaż menedżerowie pamięci mogą zrobić coś sprytnego, jeśli jest wystarczająco dużo dostępnej ciągłej wolnej pamięci.

Tomasz
źródło
6
@bodacydo: Nie wdrażaj rosnącego bufora, po prostu go używaj std::vector- będzie on wzrastał automatycznie, gdy zajdzie taka potrzeba i możesz wstępnie przydzielić pamięć, jeśli chcesz ( reserve()).
ostry ząb
4
Użyj std :: vector <T>. Po to jest. W C ++ nie ma żadnego powodu, aby używać new / delete / new [] / delete [] samodzielnie, chyba że jawnie piszesz klasy zarządzania zasobami.
Puppy
4
@bod: Tak, może. (Tak std::stringprzy okazji.)
fredoverflow
1
Tak, nie ma problemu. Nawet std::stringmoże to zrobić. Nawiasem mówiąc, jest szansa, że ​​odczyt danych również może zostać uproszczony. Jak czytasz swoje dane?
Thomas
2
Brzmi jak thevector.resize(previous_size + incoming_size), a po nim memcpy(lub podobny) do &thevector[previous_size], jest to, czego potrzebujesz. Dane wektora są przechowywane „jak tablica”.
Thomas
36

Zmiana rozmiaru w C ++ jest niewygodna ze względu na potencjalną potrzebę wywoływania konstruktorów i destruktorów.

Nie sądzę, aby istniał podstawowy powód, dla którego w C ++ nie można było mieć resize[]operatoranew[] a delete[]to zrobiło coś podobnego do tego:

newbuf = new Type[newsize];
std::copy_n(oldbuf, std::min(oldsize, newsize), newbuf);
delete[] oldbuf;
return newbuf;

Oczywiście oldsize zostałby pobrany z tajnego miejsca, w którym się znajduje delete[], i Typepochodziłby z typu operandu. resize[]nie powiedzie się, gdy Typ nie jest kopiowalny - co jest poprawne, ponieważ takich obiektów po prostu nie można przenieść. Na koniec powyższy kod domyślnie konstruuje obiekty przed ich przypisaniem, czego nie chcesz, ponieważ jest to rzeczywiste zachowanie.

Możliwa jest optymalizacja, w której newsize <= oldsize polegająca na wywołaniu destruktorów obiektów „poza końcem” nowo utworzonej tablicy i nic więcej. Standard musiałby zdefiniować, czy ta optymalizacja jest wymagana (jak w przypadku resize()wektora), dozwolona, ​​ale nieokreślona, ​​dozwolona, ​​ale zależna od implementacji, czy zabroniona.

Pytanie, które powinieneś sobie wtedy zadać, brzmi: „czy rzeczywiście warto to zapewnić, biorąc pod uwagę, że vectorrównież to robi, i jest zaprojektowany specjalnie w celu zapewnienia kontenera z możliwością zmiany rozmiaru (ciągłej pamięci - to wymaganie zostało pominięte w C ++ 98, ale poprawione w C ++ 03), które są lepsze niż tablice ze sposobami robienia rzeczy w C ++? ”

Myślę, że powszechnie uważa się, że odpowiedź brzmi „nie”. Jeśli chcesz robić bufory o zmiennym rozmiarze na sposób C, użyj malloc / free / realloc, które są dostępne w C ++. Jeśli chcesz tworzyć bufory o zmiennym rozmiarze w sposób C ++, użyj wektora (lub deque, jeśli w rzeczywistości nie potrzebujesz ciągłego przechowywania). Nie próbuj mieszać tych dwóch, używając new[]dla surowych buforów, chyba że implementujesz kontener podobny do wektora.

Steve Jessop
źródło
0

Oto przykład std :: move implementujący prosty wektor z realloc (* 2 za każdym razem, gdy osiągniemy limit). Jeśli istnieje sposób, aby zrobić coś lepszego niż kopia, którą mam poniżej, daj mi znać.

Kompiluj jako:

  g++ -std=c++2a -O2 -Wall -pedantic foo.cpp

Kod:

#include <iostream>
#include <algorithm>

template<class T> class MyVector {
private:
    T *data;
    size_t maxlen;
    size_t currlen;
public:
    MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
    MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }

    MyVector<T> (const MyVector& o) {
        std::cout << "copy ctor called" << std::endl;
        data = new T [o.maxlen];
        maxlen = o.maxlen;
        currlen = o.currlen;
        std::copy(o.data, o.data + o.maxlen, data);
    }

    MyVector<T> (const MyVector<T>&& o) {
        std::cout << "move ctor called" << std::endl;
        data = o.data;
        maxlen = o.maxlen;
        currlen = o.currlen;
    }

    void push_back (const T& i) {
        if (currlen >= maxlen) {
            maxlen *= 2;
            auto newdata = new T [maxlen];
            std::copy(data, data + currlen, newdata);
            if (data) {
                delete[] data;
            }
            data = newdata;
        }
        data[currlen++] = i;
    }

    friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
        auto s = o.data;
        auto e = o.data + o.currlen;;
        while (s < e) {
            os << "[" << *s << "]";
            s++;
        }
        return os;
    }
};

int main() {
    auto c = new MyVector<int>(1);
    c->push_back(10);
    c->push_back(11);
}
Neil McGill
źródło
-7

spróbuj czegoś takiego:

typedef struct Board
{
    string name;
    int size = 0;
};

typedef struct tagRDATA
{
    vector <Board> myBoards(255);

    // Board DataBoard[255];
    int SelectedBoard;

} RUNDATA;

Wektor będzie narzekać. Dlatego nadal istnieją tablice, malloc i new.

jakiś gość
źródło
12
Nie, nie dlatego. I nie rozumiem, jak to w żaden sposób odpowiada na to pytanie. Albo dlaczego używasz typedefwszędzie tak, jakbyś pisał C.
Wyścigi lekkości na orbicie