BLI: improve exception safety of memory utils

Even if we do not use exception in many places in Blender, our core C++ library
should become exception safe. Otherwise, we don't even have the option
to work with exceptions if we decide to do so.
This commit is contained in:
Jacques Lucke
2020-07-06 09:08:53 +02:00
parent 703a73fa84
commit 572c48cf98
5 changed files with 212 additions and 33 deletions

View File

@@ -19,6 +19,9 @@
/** \file
* \ingroup bli
* Some of the functions below have very similar alternatives in the standard library. However, it
* is rather annoying to use those when debugging. Therefore, some more specialized and easier to
* debug functions are provided here.
*/
#include <memory>
@@ -28,10 +31,39 @@
namespace blender {
/**
* Call the destructor on n consecutive values. For trivially destructible types, this does
* nothing.
*
* Exception Safety: Destructors shouldn't throw exceptions.
*
* Before:
* ptr: initialized
* After:
* ptr: uninitialized
*/
template<typename T> void destruct_n(T *ptr, uint n)
{
static_assert(std::is_nothrow_destructible_v<T>,
"This should be true for all types. Destructors are noexcept by default.");
/* This is not strictly necessary, because the loop below will be optimized away anyway. It is
* nice to make behavior this explicitly, though. */
if (std::is_trivially_destructible<T>::value) {
return;
}
for (uint i = 0; i < n; i++) {
ptr[i].~T();
}
}
/**
* Call the default constructor on n consecutive elements. For trivially constructible types, this
* does nothing.
*
* Exception Safety: Strong.
*
* Before:
* ptr: uninitialized
* After:
@@ -45,36 +77,23 @@ template<typename T> void default_construct_n(T *ptr, uint n)
return;
}
for (uint i = 0; i < n; i++) {
new ((void *)(ptr + i)) T;
uint current = 0;
try {
for (; current < n; current++) {
new ((void *)(ptr + current)) T;
}
}
}
/**
* Call the destructor on n consecutive values. For trivially destructible types, this does
* nothing.
*
* Before:
* ptr: initialized
* After:
* ptr: uninitialized
*/
template<typename T> void destruct_n(T *ptr, uint n)
{
/* This is not strictly necessary, because the loop below will be optimized away anyway. It is
* nice to make behavior this explicitly, though. */
if (std::is_trivially_destructible<T>::value) {
return;
}
for (uint i = 0; i < n; i++) {
ptr[i].~T();
catch (...) {
destruct_n(ptr, current);
throw;
}
}
/**
* Copy n values from src to dst.
*
* Exception Safety: Basic.
*
* Before:
* src: initialized
* dst: initialized
@@ -92,6 +111,8 @@ template<typename T> void initialized_copy_n(const T *src, uint n, T *dst)
/**
* Copy n values from src to dst.
*
* Exception Safety: Strong.
*
* Before:
* src: initialized
* dst: uninitialized
@@ -101,14 +122,23 @@ template<typename T> void initialized_copy_n(const T *src, uint n, T *dst)
*/
template<typename T> void uninitialized_copy_n(const T *src, uint n, T *dst)
{
for (uint i = 0; i < n; i++) {
new ((void *)(dst + i)) T(src[i]);
uint current = 0;
try {
for (; current < n; current++) {
new ((void *)(dst + current)) T(src[current]);
}
}
catch (...) {
destruct_n(dst, current);
throw;
}
}
/**
* Move n values from src to dst.
*
* Exception Safety: Basic.
*
* Before:
* src: initialized
* dst: initialized
@@ -126,6 +156,8 @@ template<typename T> void initialized_move_n(T *src, uint n, T *dst)
/**
* Move n values from src to dst.
*
* Exception Safety: Basic.
*
* Before:
* src: initialized
* dst: uninitialized
@@ -135,8 +167,19 @@ template<typename T> void initialized_move_n(T *src, uint n, T *dst)
*/
template<typename T> void uninitialized_move_n(T *src, uint n, T *dst)
{
for (uint i = 0; i < n; i++) {
new ((void *)(dst + i)) T(std::move(src[i]));
static_assert(std::is_nothrow_move_constructible_v<T>,
"Ideally, all types should have this property. We might have to remove this "
"limitation of a real reason comes up.");
uint current = 0;
try {
for (; current < n; current++) {
new ((void *)(dst + current)) T(std::move(src[current]));
}
}
catch (...) {
destruct_n(dst, current);
throw;
}
}
@@ -144,6 +187,8 @@ template<typename T> void uninitialized_move_n(T *src, uint n, T *dst)
* Relocate n values from src to dst. Relocation is a move followed by destruction of the src
* value.
*
* Exception Safety: Basic.
*
* Before:
* src: initialized
* dst: initialized
@@ -161,6 +206,8 @@ template<typename T> void initialized_relocate_n(T *src, uint n, T *dst)
* Relocate n values from src to dst. Relocation is a move followed by destruction of the src
* value.
*
* Exception Safety: Basic.
*
* Before:
* src: initialized
* dst: uninitialized
@@ -177,6 +224,8 @@ template<typename T> void uninitialized_relocate_n(T *src, uint n, T *dst)
/**
* Copy the value to n consecutive elements.
*
* Exception Safety: Basic.
*
* Before:
* dst: initialized
* After:
@@ -192,6 +241,8 @@ template<typename T> void initialized_fill_n(T *dst, uint n, const T &value)
/**
* Copy the value to n consecutive elements.
*
* Exception Safety: Strong.
*
* Before:
* dst: uninitialized
* After:
@@ -199,8 +250,15 @@ template<typename T> void initialized_fill_n(T *dst, uint n, const T &value)
*/
template<typename T> void uninitialized_fill_n(T *dst, uint n, const T &value)
{
for (uint i = 0; i < n; i++) {
new ((void *)(dst + i)) T(value);
uint current = 0;
try {
for (; current < n; current++) {
new ((void *)(dst + current)) T(value);
}
}
catch (...) {
destruct_n(dst, current);
throw;
}
}