10

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:

  1. Why is it a bad idea to be able to realloc objects?
  2. Why is it a bad idea for an object to change its size? (Implementing a collection seems a perfectly valid reason for an object to change its size.)
  3. What rule would be broken in these cases and why is this rule objectively good?
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Rares Dima
  • 1,575
  • 1
  • 15
  • 38
  • 4
    If you need to reallocate an "array" you probably looking for `std::vector` anyway so don't need an explicit `realloc`. – Some programmer dude Jul 01 '21 at 13:15
  • The closest equivalent would be using [placement new](https://stackoverflow.com/questions/15061021/realloc-equivalent-in-c) – Cory Kramer Jul 01 '21 at 13:15
  • To guess? C++ is all about deterministic construction and destruction. If an expression like `realloc` did exist... it would not be deterministic. It may just create new objects... or it may destroy some existing ones... or it may destroy the entire existing buffer and reinitialize a new one. That's.... meh. It's better to do such things with a library component than a core language piece, IMHO. – StoryTeller - Unslander Monica Jul 01 '21 at 13:17
  • A `realloc` for `new[]` might be reasonable, but that wouldn't change the size of an object, just the number of allocated objects. But that's already covored by `std::vector`. `realloc` for `new` makes absolutely no sense. The size of an object is a compile-time constant. PS: I don't see placement new as an equivalent, because it's not reallocating. It's just calling a constructor in already allocated memory. – Stefan Riedel Jul 01 '21 at 13:17
  • And see https://stackoverflow.com/questions/16714937/what-is-c-version-of-realloc-to-allocate-the-new-buffer-and-copy-the-conten/16714982 – L. Scott Johnson Jul 01 '21 at 13:18
  • @StefanRiedel the array is itself an object, or do you mean that it would end the lifetime of one array and start the lifetime of another, with (some) subobjects living on? – Caleth Jul 01 '21 at 13:19
  • 2
    @StoryTeller-UnslanderMonica "C++ is all about deterministic ..." *nervously whistles in UB* – Hatted Rooster Jul 01 '21 at 13:20
  • @HattedRooster - C++ gives you a shot gun to aim at your foot, that's true. But it doesn't *try* to shoot you itself. Much of the spec tries to reliably define object lifetime. It's not *perfect*, but it is a core design guideline of the language. – StoryTeller - Unslander Monica Jul 01 '21 at 13:22
  • @Caleth Well yes and no. It kinda violates the `size (can be determined with sizeof);` rule for objects, because you can't get that from an array allocated with `new[]`. See [Object](https://en.cppreference.com/w/cpp/language/object). I mean, it has a size, but you can't retrieve that. – Stefan Riedel Jul 01 '21 at 13:24
  • @StefanRiedel that's because you can't get at the array, all you have is a pointer to it – Caleth Jul 01 '21 at 13:35

3 Answers3

14

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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • What objects are not trivially copyable? I am trying to but i cannot think of an example. – Rares Dima Jul 01 '21 at 13:22
  • @RaresDima: Pick a standard library type. You've got roughly a 10% chance or so of getting a trivially copyable type. – Nicol Bolas Jul 01 '21 at 13:23
  • The object which stores a pointer to a buffer inside the object itself is one example. – Tomek Jul 01 '21 at 13:23
  • 3
    @RaresDima -`class A { int i; int *pi = &i; ... };` - you can't just bitwise transfer it. Now consider that this is what the SSO is based on, and you've got a major problem. – StoryTeller - Unslander Monica Jul 01 '21 at 13:24
  • 5
    @RaresDima `std::string` is a perfect example. – NathanOliver Jul 01 '21 at 13:25
  • @StoryTeller-UnslanderMonica SSO? – 0x5453 Jul 01 '21 at 13:25
  • 2
    @0x5453 - small string optimization – StoryTeller - Unslander Monica Jul 01 '21 at 13:26
  • @Daniel Langr, I don't mean a pointer to member. I mean a pointer. Which points to data inside the object itself. – Tomek Jul 01 '21 at 13:26
  • @DanielLangr: In order to make copies of that class actually maintain the invariant, copying/moving must initialize the pointer, not from the copy, but from its own `&i`. So the copy/move constructors cannot be trivial. – Nicol Bolas Jul 01 '21 at 13:28
  • 1
    Moving such object to new location invalidates its state. For me - it means it is NOT trivially copyable. – Tomek Jul 01 '21 at 13:28
  • @Tomek Logically, yes. Technically, no: https://godbolt.org/z/E7T4Gavxv. I pointed to that pointer-to-member does not imply trivial-copyability **by itself**. Yes, we all know what you meant, but I am afraid OP is not that much experienced to recognize this. – Daniel Langr Jul 01 '21 at 13:32
  • 1
    @Tomek if StoryTeller's `A` doesn't have special members in the `...`, it is trivially copyable. In that case it's a bad class – Caleth Jul 01 '21 at 13:33
6

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.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • What objects are not trivially copyable? I am trying to but i cannot think of an example – Rares Dima Jul 01 '21 at 13:23
  • 1
    @RaresDima For instance, objects of all classes with custom destructors. For details, see https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable. – Daniel Langr Jul 01 '21 at 13:23
  • why would the same destructors not work after you have copied the object bit by bit? – Rares Dima Jul 01 '21 at 13:25
  • @RaresDima -- Try `std::string`. – PaulMcKenzie Jul 01 '21 at 13:26
  • @RaresDima consider `std::unique_ptr`. If you bitwise copy it, there are now two objects that will call `delete` on the same pointer value – Caleth Jul 01 '21 at 13:28
  • @RaresDima That depends on the particular type. For instance, in case of `std::unique_ptr`, this would obviously result in double-delete. But you shouldn't care about what can happen. Doing bitwise copy on non-trivially-copyable object is _undefined behavior_. – Daniel Langr Jul 01 '21 at 13:29
  • @DanielLangr what if the destructor was not called at all? Do a trivial copy on the `unique_ptr` and just free the memory of the other one. Because the desired outcome is not a new object, but rather for that object to continue living like nothing happened. – Rares Dima Jul 01 '21 at 14:26
  • @RaresDima Maybe you should first of all specify why are you even care about this problem. Why don't you simply use `std::vector` which does the "true C++ reallocation", such that it supports trivially-copyable types, and non-trivially-copyable types, where it uses proper technique (content moving/copying). – Daniel Langr Jul 01 '21 at 16:42
  • @RaresDima BTW, what you propose won't work for the already mentioned case where a member pointer points to a member buffer. (This is, BTW, how `std::string` is implemented in GNU libstdc++ or Microsoft STL; look for _small string optimization_.) Then, if you byte-copy such an object into a new memory location and release the original memory, the pointer will become invalid. – Daniel Langr Jul 01 '21 at 17:02
0

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?

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • I'm skeptical on your last comment on `std::vector`, since the "very good performance" depends strongly on the data and use-case. An unknown-sized range with a series of `push_back`s of large objects will not have great performance over the ability to expand in-place. There are plenty of talks and articles on this very topic that show better growth characteristics when using in-place expansion. Also not all `std::vector` implementations grow by a factor of 2 either -- Dinkumware uses (or used?) 1.5, so this is not a reliable/portable expectation for cross-platform data. – Human-Compiler Jul 01 '21 at 14:17
  • 1
    @Human-Compiler Everything depends on the data and use-case. `std::vector` has survived without `realloc` for 20+ years, which *probably* means it is good enough for most of its users. If it turns out to be not good enough after all, I will be the first to welcome our re-new-ing overlords. – n. m. could be an AI Jul 01 '21 at 14:47