How do you 'realloc' in C++?
Asked Answered
H

4

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.

Heaviness answered 14/8, 2010 at 10:39 Comment(3)
Stroustrup answered this a long time back, see: 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.)Brochu
The answer referenced by @Brochu is now at: stroustrup.com/bs_faq2.html#renew - and Cline's FAQ is now part of the super FAQ: isocpp.org/faqTittle
@Brochu The link is broken, can you please update the reference?Disingenuous
M
64

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.

Mildredmildrid answered 14/8, 2010 at 11:11 Comment(8)
Shouldnt the memset line be memset(t, 0, sizeof(T) * n);? n instead of m?Brambling
@anthom yes. it should really be Type* t = static_cast<Type*>(malloc(n * sizeof *t));Knar
With C++11 one would now use t.data() instead of &t[0]Balbriggan
How can you then delete this?Data
@a3mlord: What do you mean? Let it fall out of scope, and it's gone.Redintegration
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 spaceMcbroom
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.Perfectionist
vector::resize is fundamentally different from realloc. See https://mcmap.net/q/212207/-is-std-vector-memory-freed-upon-a-clear for discussion. You must call shrink_to_fit afterwards. Even this would be approximation because shrink_to_fit is non-binding.Footplate
M
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.

Matchmark answered 14/8, 2010 at 10:42 Comment(11)
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?Heaviness
@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()).Eddieeddina
Use std::vector<T>. 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.Bustee
So should I use std::vector<char>? I am curious if std::vector<char> can contain 0 (NUL) bytes?Heaviness
@bod: Yes, it can. (So can std::string, by the way.)Ferriferous
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?Matchmark
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.Heaviness
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".Matchmark
Thomas. I just wrote my code with push_back and it didn't work. Thanks for explaining the .resize() followed by memcpy.Heaviness
@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.Strigil
@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.Diversion
R
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.

Refection answered 14/8, 2010 at 12:36 Comment(1)
There were some interesting proposals related to resizing way back in 2006 which didn't gain traction at the time open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1953.html open-std.org/jtc1/sc22/wg14/www/docs/n1085.htm open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2045.html . I don't know if there are more recent versions.Opponent
P
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);
}
Paleobiology answered 13/5, 2020 at 19:20 Comment(2)
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.Antigen
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).Antigen

© 2022 - 2024 — McMap. All rights reserved.