102

How can I realloc in C++? It seems to be missing from the language - there is new and delete but not resize!

I need it because as my program reads more data, I need to reallocate the buffer to hold it. I don't think deleteing the old pointer and newing a new, bigger one, is the right option.

Jarvis
  • 8,494
  • 3
  • 27
  • 58
bodacydo
  • 75,521
  • 93
  • 229
  • 319
  • 11
    Stroustrup answered this a long time back, see: http://www2.research.att.com/~bs/bs_faq2.html#renew (That's a good start if you are new to C++ along with Cline's C++ FAQ.) – dirkgently Aug 14 '10 at 11:20
  • 12
    The answer referenced by @dirkgently is now at: http://www.stroustrup.com/bs_faq2.html#renew - and Cline's FAQ is now part of the super FAQ: https://isocpp.org/faq – maxschlepzig Oct 10 '15 at 12:57
  • @dirkgently The link is broken, can you please update the reference? – CodeTalker Jul 03 '22 at 16:02

4 Answers4

62

Use ::std::vector!

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

becomes

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

Then

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

becomes

t.resize(n2);

If you want to pass pointer into function, instead of

Foo(t)

use

Foo(&t[0])

It is absolutely correct C++ code, because vector is a smart C-array.

Community
  • 1
  • 1
f0b0s
  • 2,978
  • 26
  • 30
  • 2
    Shouldnt the memset line be memset(t, 0, sizeof(T) * n);? n instead of m? – Raphael Mayer Mar 03 '15 at 21:07
  • 1
    @anthom yes. it should really be `Type* t = static_cast(malloc(n * sizeof *t));` – Ryan Haining Oct 14 '15 at 17:49
  • 2
    With C++11 one would now use `t.data()` instead of `&t[0]` – knedlsepp Oct 15 '15 at 09:29
  • 1
    How can you then delete this? – a3mlord Oct 22 '15 at 18:27
  • @a3mlord: What do you mean? Let it fall out of scope, and it's gone. – Lightness Races in Orbit Apr 20 '16 at 14:25
  • When you want to build your own datastructure you should handle this completely. Basically, if your buffer is full you'll need to do the following operations: 1. Multiply your buffer capacity( x2 or x1.5 like Visual C++) 2. Allocate a memory space of your new capacity 3. Move your old buffer into the new memory space 4. Free the memory of your old buffer 5. Set your datastructure to point to the new memory space – Olivier D'Ancona May 02 '21 at 07:40
  • looking to specification, `realloc` expanding or contracting the existing area pointed to by ptr, if possible. **The contents of the area remain unchanged** up to the lesser of the new and old sizes. If the area is expanded, the contents of the new part of the array are undefined. So it looks like using `vector` not absolutely correct advice. I'm using new+memcpy+delete combination when need migrate legacy to the new style. – Dmitry Ivanov Jun 08 '22 at 19:54
  • `vector::resize` is fundamentally different from `realloc`. See stackoverflow.com/a/67819492/1150462 for discussion. You must call `shrink_to_fit` afterwards. Even this would be approximation because `shrink_to_fit` is non-binding. – xuhdev May 24 '23 at 00:24
54

The right option is probably to use a container that does the work for you, like std::vector.

new and delete cannot resize, because they allocate just enough memory to hold an object of the given type. The size of a given type will never change. There are new[] and delete[] but there's hardly ever a reason to use them.

What realloc does in C is likely to be just a malloc, memcpy and free, anyway, although memory managers are allowed to do something clever if there is enough contiguous free memory available.

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • 2
    So what would be the right way to implement a growing buffer in C++? Currently I have `char *buf = (char *)malloc(size)`, then when it becomes too small I do `buf = realloc(size + more_size); size += more_size`. How can I do it with vector? – bodacydo Aug 14 '10 at 10:44
  • 6
    @bodacydo: Don't implement the growing buffer, just use `std::vector` - it will grow automatically when needed and you can pre-allocate memory if you want (`reserve()`). – sharptooth Aug 14 '10 at 10:47
  • 4
    Use std::vector. That's what it's for. In C++, there is no reason whatsoever to use new/delete/new[]/delete[] yourself, unless you're explicitly writing resource management classes. – Puppy Aug 14 '10 at 10:49
  • So should I use `std::vector`? I am curious if `std::vector` can contain `0` (NUL) bytes? – bodacydo Aug 14 '10 at 10:50
  • 4
    @bod: Yes, it can. (So can `std::string`, by the way.) – fredoverflow Aug 14 '10 at 10:51
  • 1
    Yes, it can, no problem. Even `std::string` can do that. By the way, there's a chance that your data reading can be simplified, too. How are you reading your data? – Thomas Aug 14 '10 at 10:52
  • Thomas: the data comes in from the network, and I don't really have control over how much of it is gonna come in. It works like this - a packet comes in and says 100 bytes are following. So I allocate 100 bytes, then there are several types of packets, one might say "200 more bytes", so I do realloc and size += 200. – bodacydo Aug 14 '10 at 11:09
  • 2
    Sounds like `thevector.resize(previous_size + incoming_size)`, followed by a `memcpy` (or similar) into `&thevector[previous_size]`, is what you need. The vector's data is guaranteed to be stored "like an array". – Thomas Aug 14 '10 at 11:32
  • Thomas. I just wrote my code with `push_back` and it didn't work. Thanks for explaining the .resize() followed by memcpy. – bodacydo Aug 14 '10 at 11:36
  • @bodacydo: Read the docs. The `v.push_back(d)` is semantically equivalent to `v.resize(v.size()+1); v.back() = d;` That will allocate an extra element in the array and insert the data in that position. – David Rodríguez - dribeas Aug 14 '10 at 12:12
  • 1
    @dribeas: Better than that; `v.push_back(d)` is semantically equivalent to `v.resize(v.size() + 1, d)`; there's no requirement for a default constructed temporary or an assignment operation. – CB Bailey Aug 14 '10 at 12:26
44

Resizing in C++ is awkward because of the potential need to call constructors and destructors.

I don't think there's a fundamental reason why in C++ you couldn't have a resize[] operator to go with new[] and delete[], that did something similar to this:

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

Obviously oldsize would be retrieved from a secret location, same is it is in delete[], and Type would come from the type of the operand. resize[] would fail where the Type is not copyable - which is correct, since such objects simply cannot be relocated. Finally, the above code default-constructs the objects before assigning them, which you would not want as the actual behaviour.

There's a possible optimisation where newsize <= oldsize, to call destructors for the objects "past the end" of the newly-ensmallened array and do nothing else. The standard would have to define whether this optimisation is required (as when you resize() a vector), permitted but unspecified, permitted but implementation-dependent, or forbidden.

The question you should then ask yourself is, "is it actually useful to provide this, given that vector also does it, and is designed specifically to provide a resize-able container (of contiguous memory--that requirement omitted in C++98 but fixed in C++03) that's a better fit than arrays with the C++ ways of doing things?"

I think the answer is widely thought to be "no". If you want to do resizeable buffers the C way, use malloc / free / realloc, which are available in C++. If you want to do resizeable buffers the C++ way, use a vector (or deque, if you don't actually need contiguous storage). Don't try to mix the two by using new[] for raw buffers, unless you're implementing a vector-like container.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • There were some interesting proposals related to resizing way back in 2006 which didn't gain traction at the time https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1953.html https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1085.htm https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2045.html . I don't know if there are more recent versions. – Bruce Adams May 10 '22 at 09:29
0

Here's a std::move example implementing a simple vector with a realloc (*2 each time we hit the limit). If there's a way to do better than the copy I have below, pls let me know.

Compile as:

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

Code:

#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);
}
Goblinhack
  • 2,859
  • 1
  • 26
  • 26
  • Your implementation example has some issues. Of the many, the following are the most relevant: the move constructor simply assigns the members of the source object to the target object, leaving both to point to the same buffer, and therefore a update operation to the resources of ; the move constructor does not accept a const parameter; the `push_back()` member function should not directly assign the value to a array position, because it is a uninitialized buffer of memory, and then it could result in UB if the copy constructor is not trivial. – LoS Aug 23 '23 at 18:29
  • Most important, in the absence of a user-defined destructor, resources can not be released properly (for example, if `delete[]` is not invoked at object destruction and memory was previously allocated with `new[]`, a memory leak occurs). – LoS Aug 23 '23 at 18:30