Understanding std::uninitialized_fill in C++
std::uninitialized_fill is a low-level C++ utility that
initializes a specified range of uninitialized memory by
copy-constructing elements with a given value. This article explains the
primary purpose of std::uninitialized_fill, why it is
essential when managing raw memory, how it differs from standard
assignment algorithms like std::fill, and how it ensures
exception safety in custom data structures.
The Problem with Standard Assignment on Raw Memory
When allocating dynamic memory using mechanisms such as
malloc, ::operator new, or
std::allocator<T>::allocate, the result is
uninitialized storage. At this stage, memory has been reserved, but no
object lifetimes have begun because no constructors have been
executed.
Using algorithms like std::fill on raw memory is
undefined behavior for non-trivial types. std::fill relies
on the copy assignment operator (operator=), which assumes
that a valid, fully constructed object already exists at the target
location. Invoking an assignment operator on uninitialized storage can
lead to memory corruption, segmentation faults, or attempts to read
invalid pointers.
How std::uninitialized_fill Works
Defined in the <memory> header,
std::uninitialized_fill bridges the gap between raw
allocation and object initialization. Its signature generally resembles
the following:
template <class ForwardIt, class T>
void uninitialized_fill(ForwardIt first, ForwardIt last, const T& value);Instead of using assignment, it iterates through the range
[first, last) and constructs an object at each address
using placement new:
::new (static_cast<void*>(std::addressof(*it))) ValueType(value);By using placement new, it begins the lifetime of each object directly inside the pre-allocated raw memory buffer via copy construction.
Guaranteed Exception Safety
A critical feature of std::uninitialized_fill is its
adherence to the basic exception guarantee:
- As it iterates through the range, it keeps track of all successfully constructed objects.
- If the copy constructor of an element throws an exception during initialization, the algorithm catches the exception.
- It then automatically destroys all objects that were already constructed in the range, calling their destructors in reverse order.
- Finally, it rethrows the original exception.
This automatic cleanup prevents partial initialization states and
resource leaks, which would otherwise require complex and error-prone
try-catch blocks if implemented manually.
Practical Use Case: Custom Containers
The most common use case for std::uninitialized_fill is
the implementation of custom containers, such as dynamic arrays or
vectors.
#include <memory>
#include <iostream>
template <typename T>
class SimpleBuffer {
std::allocator<T> alloc;
T* data;
std::size_t count;
public:
SimpleBuffer(std::size_t n, const T& val) : count(n) {
// Step 1: Allocate raw, uninitialized memory
data = alloc.allocate(count);
try {
// Step 2: Construct objects in raw memory
std::uninitialized_fill(data, data + count, val);
} catch (...) {
alloc.deallocate(data, count);
throw;
}
}
~SimpleBuffer() {
// Step 3: Destroy constructed objects
std::destroy(data, data + count);
// Step 4: Deallocate raw memory
alloc.deallocate(data, count);
}
};In this example, separating raw memory allocation from object construction allows the container to handle memory efficiently while correctly managing object lifetimes and exceptions.