Reputation: 1767
As the title says, I know there is no equivalent to C's realloc
in the new
/delete
family of operators.
I have already found this question that lightly touches on the subject but it doesn't really answer the "why".
My questions are:
Upvotes: 11
Views: 1381
Reputation: 2984
Probably, a realloc() equivalent doesn't exist in C++, because so far nobody pushed it hard enough to get it into the standard. On the contrary, C++23 did add a new method allocate_at_least()
to std::allocator
, which shows, that performance enhancing changes and extensions to the allocator system are happening, though at a slow rate.
The rationale behind allocate_at_least(n)
is, that it may allocate more than n
objects of the requested type, if the higher number better fits to the current system. For example, modern processors use 4 kiB small, 2 MiB large and 1 GiB huge pages for memory management. So, if an application requests 1950 kiB, the underlying allocator may round that up to a full 2048 kiB = 2 MiB large page instead. With allocate_at_least()
(as opposed to just allocate()
), the memory management can report back the actual allocation size to the application. If, for example, a std::vector
is stored in the allocated space, it can take up proportionally more elements, before another reallocation is required.
try_realloc_inplace()
?But back to the original question: As others have pointed out correctly, a C-semantic realloc()
is not possible in C++, because in the case, that growing an existing allocation fails, the data may not be copied over blindly from the old location to the new location, but will need to be moved over by the C++ application maintaining that data instead. But it wouldn't be hard to add a new method try_realloc_inplace(T* p, std::size_t oldsz, std::size_t newsz)
to std::allocator
with the following properties:
If newsz
equals zero, it performs the same action as deallocate(p, oldsz)
and then returns 0 as result.
If newsz
equals oldsz
, it does nothing and returns newsz
.
If newsz
is different from oldsz
, and it is possible to adjust the size of the current allocation, it will change the size of the allocation and return the actual new size after that change. The returned value must be at least newsz
and must be less than oldsz
, if newsz
was less than oldsz
. So it is not allowed to make the allocation larger, if a shrink was requested, and it is also not allowed to return an allocation, that would be too small to hold newsz
elements.
If try_realloc_inplace()
fails to change the size of the allocation, it just returns oldsz
instead. It is then the responsibility of the application to allocate memory, move over the existing elements and deallocate the old memory.
new
and delete
?The C++ standard requires, that all methods of the default allocator, std::allocator
, that allocate or deallocate memory, do this through operator new(std::size_t)
and operator delete(void* p)
, respectively. This actually makes implementing std::allocator::allocate_at_least()
next to impossible, because new
doesn't return any information about the actual size allocated. And even, if there was some secret side channel, on which new
returned the actual size of the last allocation (for example, via a thread-local variable), the C++ standard library cannot use that side channel securely, because users may override new(size_t)
and delete(void* p)
and the standard allocator has no portable way of knowing, if it is calling the default or overriden operators!
The same is true for try_realloc_inplace()
: As it definitely extends the capabilities of operator new
, it cannot be implemented in std::allocator
, but only in private allocators. And even for private allocators, it is just an opt-in feature, that the makers of the allocator may or may not provide. So, except for adding some wording to the C++ standard and for adding a templated method to std::allocator_traits
, which calls try_realloc_inplace()
, if it is available, there wouldn't be any work for the major compiler and standard library teams for now. But providers of highly specialized or optimized allocation libraries would become the freedom to add this feature. And the few containers like std::vector
or std::unordered_map
, that require large contiguous chunks of memory (in case of vector
for the elements itself, in case of unordered_map
for the hash table) could start calling try_realloc_inplace()
, before they fall back to the (already implemented!) default behavior of allocating new memory and then copying the existing elements. That wouldn't be much work either.
At one point in time, a new C++ version will come with a break of the existing ABI, though. For example, the chunk size of std::deque
is ridiculously small, UTF-8-support is still awkward, run time type information is extremely limited and cannot be extended, even, if requested, and many more. So a future C++ standard will come, that won't be able to link with code, that was compiled with older C++ standards. It will be bad time for OS vendors, as they will need to deliver many libraries twice (once in the new ABI, once in the old ABI), but performance will increase and the usability of C++ will increase, too.
And yeah, as part of that future ABI change, they will for sure lift the requirement, that std::allocator
uses new
and delete
. They may become independent, or they may even flip things round, that new
and delete
use std::allocator
under the hood. Whatever they do, though, at the time of the ABI break, it will be possible to make std::allocator
a flexible allocator. So yes, push for it now, that it becomes good then ;-)
Upvotes: 0
Reputation: 120021
There is no such functionality because the C library doesn't provide a suitable interface to implement one. new
and delete
are implementable in terms of malloc
and free
, but the hypothetical renew
is not implementable in terms of realloc
, because you cannot move bytes of a C++ object (others have pointed out and explained this fact). When C++ was in its early stages, it was an important consideration, and it still is to some extent. You usually don't want to write your own, possibly inferior, allocator, when you can just piggy-back on top of very mature and proven malloc
and friends.
It is possible to implement renew
in terms of a low-level allocator that provides a function like try_realloc
. Check if the block can grow in situ, grow it if yes, allocate a new block and move existing objects to it if not. Win-win? Apparently it turns out that this functionality is not too important. std::vector
de-facto policy of doubling the allocated size works just fine and provides very good performance, so why bother?
Upvotes: 0
Reputation: 473946
Realloc has two behaviors, one of them is not acceptable in the C++ object model. Realloc can increase the size of a piece of storage, or it can allocate new storage and copy everything from the old storage into the new.
The thing is, C++ doesn't think of objects as just bags of bits. They're living, breathing types that hold invariants. And some of those invariants don't tolerate having their bits copied around well.
In C++, copying an object's bits does not mean you have effectively copied the object. This is only allowed for trivially copyable types, and there are plenty of types that aren't trivially copyable.
As such, a C++ realloc equivalent cannot be used on any allocation. You would need to split the call into two separate calls: one that attempts to expand the memory and does nothing if it can't, and the regular heap allocation call into which you would manually copy using existing C++ techniques.
As one example, many std::list
implementations store a terminator node in the std::list
object itself which is used to represent the start/end of the linked list. If you simply copied its bits, pointers to the terminator node would point to the old allocation that is now gone.
That's bad.
In order to allow an object to have arbitrary class invariants that the code which accesses those types can maintain, it is necessary to treat an object as something more than just the bits of its object representation. And most C++ types maintain some invariant for which its object representation cannot survive bitwise copying.
Upvotes: 15
Reputation: 23527
You cannot change the storage of an existing object in C++. The only what you can do is to create new objects — in "reallocated" memory — that will have the same content as the original objects. This is exactly what std::vector
is capable of.
A Problem with C++ is that this functionality involves generally much more than just copying bytes. Copying the content of objects by copying their binary representation is enabled only for a limited set of types — so-called trivially-copyalbe types. For the other ones, copy/move constructors and destructors need to be involved.
Upvotes: 6