'`make_unique_for_overwrite` still initializes `std::pair` elements

I was hoping that

auto myPairs = make_unique_for_overwrite<pair<uint64_t, void*>[]>(arraySize);

would give me uninitialized memory for my pairs. I am overwriting those later anyway and the (unnecessary) initialization is currently responsible for 120ms out of 600ms overall runtime for my algorithm.

What is the most idiomatic way to avoid this initialization?



Solution 1:[1]

According to cppreference, the default constructor of std::pair always value-initializes (aka zeroes) its elements.

The solution is to get rid of pair. You can replace it with a structure with two members.

I know I could still just allocate ... and then reinterpret_cast

Attempt to reinterpret_cast such structure to std::pair would cause undefined behavior.

Solution 2:[2]

I was hoping that [make_unique_for_overwrite] would give me uninitialized memory for my pairs.

It does not. The overload you are calling will allocate memory for an array of std::pairs, and then default-initialize each element, per https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique:

  1. Same as (2), except that the array is default-initialized. This overload participates in overload resolution only if T is an array of unknown bound. The function is equivalent to:

unique_ptr<T>(new std::remove_extent_t<T>[size])

So, this call:

auto myPairs = make_unique_for_overwrite<pair<uint64_t, void*>>[]>(arraySize);

Is effectively:

auto myPairs = unique_ptr<pair<uint64_t, void*>[]>(new pair<uint64_t, void*>[arraySize]);

std::pair's default constructor value-initializes its members, per https://en.cppreference.com/w/cpp/utility/pair/pair:

  1. Default constructor. Value-initializes both elements of the pair, first and second.
  • This constructor participates in overload resolution if and only if std::is_default_constructible_v<first_type> and std::is_default_constructible_v<second_type> are both true.
  • This constructor is explicit if and only if either first_type or second_type is not implicitly default-constructible.

Thus, you are effectively creating an array of pairs that are each initialized to {0, nullptr} before you even have a change to see the memory.

What is the most idiomatic way to avoid this initialization?

Allocate the raw memory yourself, and then placement-new the std::pair elements in it as needed.

Or use std::vector and let it handle this for you, via its reserve() method.

Solution 3:[3]

Just do

pair<uint64_t, void*>* myPairs = (pair<uint64_t, void*>*)malloc(sizeof(pair<uint64_t, void*>)*arraySize);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 thedudehimself