替换std::unique_ptr.reset为std::make_unique后程序性能骤降50%?
Let's break down what's happening here—this is a classic case of subtle initialization behavior catching developers off guard.
First, let's recap your code snippets for clarity:
Old (Fast) C++11 Code
class Foo { public: Foo(size_t size) { array.reset(new char[size]); } private: std::unique_ptr<char[]> array; };
New (Slow) C++14 Code
class Foo { public: Foo(size_t size) { array = std::make_unique<char[]>(size); } private: std::unique_ptr<char[]> array; };
The Root Cause: Default Initialization vs Value Initialization
The key difference between these two implementations boils down to how allocated memory is initialized:
- When you use
new char[size], this triggers default initialization for POD types likechar. For arrays, this means memory is allocated but left in its raw, uninitialized state (whatever garbage data was on the heap stays there). std::make_unique<char[]>(size)instead performs value initialization. Forchararrays, this translates to every single byte being set to\0(null character).
If your size is large (say, megabytes of data per allocation), that zero-initialization step adds massive, unnecessary overhead—exactly the kind of thing that can cut your test suite speed in half.
How to Verify This Theory
You can confirm the issue by manually replicating make_unique's behavior:
// This is exactly what std::make_unique<char[]>(size) does under the hood array.reset(new char[size]());
If this runs just as slow as your new code, you've confirmed zero-initialization is the culprit.
Fixes for the Performance Hit
Option 1: Use C++20's std::make_unique_for_overwrite
If you can upgrade to C++20, this is the cleanest solution. std::make_unique_for_overwrite is explicitly designed to skip initialization, matching the behavior of your original new char[size]:
array = std::make_unique_for_overwrite<char[]>(size);
Option 2: Stick with reset(new...) (C++11/14/17)
If C++20 isn't an option, there's no shame in keeping your original reset(new char[size]) pattern. It's safe here (no memory leak risk since you're immediately assigning to a unique_ptr) and avoids the zero-initialization cost entirely.
Option 3: Roll Your Own Helper (C++14/17)
If you want the safety of a make_unique-style function without the initialization overhead, create a tiny wrapper:
template<typename T> std::unique_ptr<T> make_unique_uninitialized(size_t count) { static_assert(std::is_array_v<T>, "This helper is only for array types"); using ElementType = std::remove_extent_t<T>; return std::unique_ptr<T>(new ElementType[count]); } // Usage: array = make_unique_uninitialized<char[]>(size);
A Quick Best Practice Note
While std::make_unique is generally recommended (it prevents certain edge-case memory leaks), its default value-initialization isn't always necessary. Always ask: do you actually need the array to start as all \0s?
- If you're going to overwrite the array immediately with your own data, zero-initialization is wasted work.
- If you do need the array to be zeroed, keep using
make_unique—the performance hit is unavoidable (and probably necessary).
内容的提问来源于stack exchange,提问作者user363778

