BLI: remove deduplicated memory utility functions
These functions were originally implemented because: - Not all of them existed pre C++17, but now we are using C++17. - The call stack depth is quite a bit deeper with the std functions, making debugging slower and more annoying. I didn't find this to be a problem anymore recently. No functional changes are expected.
This commit is contained in:
@@ -4,11 +4,9 @@
|
|||||||
|
|
||||||
/** \file
|
/** \file
|
||||||
* \ingroup bli
|
* \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 <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -33,280 +31,66 @@ template<typename T>
|
|||||||
inline constexpr bool is_trivially_move_constructible_extended_v =
|
inline constexpr bool is_trivially_move_constructible_extended_v =
|
||||||
is_trivial_extended_v<T> || std::is_trivially_move_constructible_v<T>;
|
is_trivial_extended_v<T> || std::is_trivially_move_constructible_v<T>;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, int64_t n)
|
template<typename T> void destruct_n(T *ptr, int64_t n)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
|
||||||
|
|
||||||
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 (is_trivially_destructible_extended_v<T>) {
|
if (is_trivially_destructible_extended_v<T>) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int64_t i = 0; i < n; i++) {
|
std::destroy_n(ptr, n);
|
||||||
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:
|
|
||||||
* ptr: initialized
|
|
||||||
*/
|
|
||||||
template<typename T> void default_construct_n(T *ptr, int64_t n)
|
template<typename T> void default_construct_n(T *ptr, int64_t n)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
std::uninitialized_default_construct_n(ptr, 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_constructible_v<T>) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t current = 0;
|
|
||||||
try {
|
|
||||||
for (; current < n; current++) {
|
|
||||||
new (static_cast<void *>(ptr + current)) T;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
destruct_n(ptr, current);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy n values from src to dst.
|
|
||||||
*
|
|
||||||
* Exception Safety: Basic.
|
|
||||||
*
|
|
||||||
* Before:
|
|
||||||
* src: initialized
|
|
||||||
* dst: initialized
|
|
||||||
* After:
|
|
||||||
* src: initialized
|
|
||||||
* dst: initialized
|
|
||||||
*/
|
|
||||||
template<typename T> void initialized_copy_n(const T *src, int64_t n, T *dst)
|
template<typename T> void initialized_copy_n(const T *src, int64_t n, T *dst)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
std::copy_n(src, n, dst);
|
||||||
|
|
||||||
for (int64_t i = 0; i < n; i++) {
|
|
||||||
dst[i] = src[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy n values from src to dst.
|
|
||||||
*
|
|
||||||
* Exception Safety: Strong.
|
|
||||||
*
|
|
||||||
* Before:
|
|
||||||
* src: initialized
|
|
||||||
* dst: uninitialized
|
|
||||||
* After:
|
|
||||||
* src: initialized
|
|
||||||
* dst: initialized
|
|
||||||
*/
|
|
||||||
template<typename T> void uninitialized_copy_n(const T *src, int64_t n, T *dst)
|
template<typename T> void uninitialized_copy_n(const T *src, int64_t n, T *dst)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
std::uninitialized_copy_n(src, n, dst);
|
||||||
|
|
||||||
int64_t current = 0;
|
|
||||||
try {
|
|
||||||
for (; current < n; current++) {
|
|
||||||
new (static_cast<void *>(dst + current)) T(src[current]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
destruct_n(dst, current);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert n values from type `From` to type `To`.
|
|
||||||
*
|
|
||||||
* Exception Safety: Strong.
|
|
||||||
*
|
|
||||||
* Before:
|
|
||||||
* src: initialized
|
|
||||||
* dst: uninitialized
|
|
||||||
* After:
|
|
||||||
* src: initialized
|
|
||||||
* dst: initialized
|
|
||||||
*/
|
|
||||||
template<typename From, typename To>
|
template<typename From, typename To>
|
||||||
void uninitialized_convert_n(const From *src, int64_t n, To *dst)
|
void uninitialized_convert_n(const From *src, int64_t n, To *dst)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
std::uninitialized_copy_n(src, n, dst);
|
||||||
|
|
||||||
int64_t current = 0;
|
|
||||||
try {
|
|
||||||
for (; current < n; current++) {
|
|
||||||
new (static_cast<void *>(dst + current)) To(static_cast<To>(src[current]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
destruct_n(dst, current);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Move n values from src to dst.
|
|
||||||
*
|
|
||||||
* Exception Safety: Basic.
|
|
||||||
*
|
|
||||||
* Before:
|
|
||||||
* src: initialized
|
|
||||||
* dst: initialized
|
|
||||||
* After:
|
|
||||||
* src: initialized, moved-from
|
|
||||||
* dst: initialized
|
|
||||||
*/
|
|
||||||
template<typename T> void initialized_move_n(T *src, int64_t n, T *dst)
|
template<typename T> void initialized_move_n(T *src, int64_t n, T *dst)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
std::copy_n(std::make_move_iterator(src), n, dst);
|
||||||
|
|
||||||
for (int64_t i = 0; i < n; i++) {
|
|
||||||
dst[i] = std::move(src[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Move n values from src to dst.
|
|
||||||
*
|
|
||||||
* Exception Safety: Basic.
|
|
||||||
*
|
|
||||||
* Before:
|
|
||||||
* src: initialized
|
|
||||||
* dst: uninitialized
|
|
||||||
* After:
|
|
||||||
* src: initialized, moved-from
|
|
||||||
* dst: initialized
|
|
||||||
*/
|
|
||||||
template<typename T> void uninitialized_move_n(T *src, int64_t n, T *dst)
|
template<typename T> void uninitialized_move_n(T *src, int64_t n, T *dst)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
std::uninitialized_copy_n(std::make_move_iterator(src), n, dst);
|
||||||
|
|
||||||
int64_t current = 0;
|
|
||||||
try {
|
|
||||||
for (; current < n; current++) {
|
|
||||||
new (static_cast<void *>(dst + current)) T(std::move(src[current]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
destruct_n(dst, current);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* After:
|
|
||||||
* src: uninitialized
|
|
||||||
* dst: initialized
|
|
||||||
*/
|
|
||||||
template<typename T> void initialized_relocate_n(T *src, int64_t n, T *dst)
|
template<typename T> void initialized_relocate_n(T *src, int64_t n, T *dst)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
|
||||||
|
|
||||||
initialized_move_n(src, n, dst);
|
initialized_move_n(src, n, dst);
|
||||||
destruct_n(src, n);
|
destruct_n(src, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* After:
|
|
||||||
* src: uninitialized
|
|
||||||
* dst: initialized
|
|
||||||
*/
|
|
||||||
template<typename T> void uninitialized_relocate_n(T *src, int64_t n, T *dst)
|
template<typename T> void uninitialized_relocate_n(T *src, int64_t n, T *dst)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
|
||||||
|
|
||||||
uninitialized_move_n(src, n, dst);
|
uninitialized_move_n(src, n, dst);
|
||||||
destruct_n(src, n);
|
destruct_n(src, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the value to n consecutive elements.
|
|
||||||
*
|
|
||||||
* Exception Safety: Basic.
|
|
||||||
*
|
|
||||||
* Before:
|
|
||||||
* dst: initialized
|
|
||||||
* After:
|
|
||||||
* dst: initialized
|
|
||||||
*/
|
|
||||||
template<typename T> void initialized_fill_n(T *dst, int64_t n, const T &value)
|
template<typename T> void initialized_fill_n(T *dst, int64_t n, const T &value)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
std::fill_n(dst, n, value);
|
||||||
|
|
||||||
for (int64_t i = 0; i < n; i++) {
|
|
||||||
dst[i] = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the value to n consecutive elements.
|
|
||||||
*
|
|
||||||
* Exception Safety: Strong.
|
|
||||||
*
|
|
||||||
* Before:
|
|
||||||
* dst: uninitialized
|
|
||||||
* After:
|
|
||||||
* dst: initialized
|
|
||||||
*/
|
|
||||||
template<typename T> void uninitialized_fill_n(T *dst, int64_t n, const T &value)
|
template<typename T> void uninitialized_fill_n(T *dst, int64_t n, const T &value)
|
||||||
{
|
{
|
||||||
BLI_assert(n >= 0);
|
std::uninitialized_fill_n(dst, n, value);
|
||||||
|
|
||||||
int64_t current = 0;
|
|
||||||
try {
|
|
||||||
for (; current < n; current++) {
|
|
||||||
new (static_cast<void *>(dst + current)) T(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
destruct_n(dst, current);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T> struct DestructValueAtAddress {
|
template<typename T> struct DestructValueAtAddress {
|
||||||
|
@@ -7,121 +7,6 @@
|
|||||||
|
|
||||||
namespace blender::tests {
|
namespace blender::tests {
|
||||||
|
|
||||||
namespace {
|
|
||||||
struct MyValue {
|
|
||||||
static inline int alive = 0;
|
|
||||||
|
|
||||||
MyValue()
|
|
||||||
{
|
|
||||||
if (alive == 15) {
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
alive++;
|
|
||||||
}
|
|
||||||
|
|
||||||
MyValue(const MyValue & /*other*/)
|
|
||||||
{
|
|
||||||
if (alive == 15) {
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
alive++;
|
|
||||||
}
|
|
||||||
|
|
||||||
~MyValue()
|
|
||||||
{
|
|
||||||
alive--;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST(memory_utils, DefaultConstructN_ActuallyCallsConstructor)
|
|
||||||
{
|
|
||||||
constexpr int amount = 10;
|
|
||||||
TypedBuffer<MyValue, amount> buffer;
|
|
||||||
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
default_construct_n(buffer.ptr(), amount);
|
|
||||||
EXPECT_EQ(MyValue::alive, amount);
|
|
||||||
destruct_n(buffer.ptr(), amount);
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(memory_utils, DefaultConstructN_StrongExceptionSafety)
|
|
||||||
{
|
|
||||||
constexpr int amount = 20;
|
|
||||||
TypedBuffer<MyValue, amount> buffer;
|
|
||||||
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
EXPECT_THROW(default_construct_n(buffer.ptr(), amount), std::exception);
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(memory_utils, UninitializedCopyN_ActuallyCopies)
|
|
||||||
{
|
|
||||||
constexpr int amount = 5;
|
|
||||||
TypedBuffer<MyValue, amount> buffer1;
|
|
||||||
TypedBuffer<MyValue, amount> buffer2;
|
|
||||||
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
default_construct_n(buffer1.ptr(), amount);
|
|
||||||
EXPECT_EQ(MyValue::alive, amount);
|
|
||||||
uninitialized_copy_n(buffer1.ptr(), amount, buffer2.ptr());
|
|
||||||
EXPECT_EQ(MyValue::alive, 2 * amount);
|
|
||||||
destruct_n(buffer1.ptr(), amount);
|
|
||||||
EXPECT_EQ(MyValue::alive, amount);
|
|
||||||
destruct_n(buffer2.ptr(), amount);
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(memory_utils, UninitializedCopyN_StrongExceptionSafety)
|
|
||||||
{
|
|
||||||
constexpr int amount = 10;
|
|
||||||
TypedBuffer<MyValue, amount> buffer1;
|
|
||||||
TypedBuffer<MyValue, amount> buffer2;
|
|
||||||
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
default_construct_n(buffer1.ptr(), amount);
|
|
||||||
EXPECT_EQ(MyValue::alive, amount);
|
|
||||||
EXPECT_THROW(uninitialized_copy_n(buffer1.ptr(), amount, buffer2.ptr()), std::exception);
|
|
||||||
EXPECT_EQ(MyValue::alive, amount);
|
|
||||||
destruct_n(buffer1.ptr(), amount);
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(memory_utils, UninitializedFillN_ActuallyCopies)
|
|
||||||
{
|
|
||||||
constexpr int amount = 10;
|
|
||||||
TypedBuffer<MyValue, amount> buffer;
|
|
||||||
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
{
|
|
||||||
MyValue value;
|
|
||||||
EXPECT_EQ(MyValue::alive, 1);
|
|
||||||
uninitialized_fill_n(buffer.ptr(), amount, value);
|
|
||||||
EXPECT_EQ(MyValue::alive, 1 + amount);
|
|
||||||
destruct_n(buffer.ptr(), amount);
|
|
||||||
EXPECT_EQ(MyValue::alive, 1);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(memory_utils, UninitializedFillN_StrongExceptionSafety)
|
|
||||||
{
|
|
||||||
constexpr int amount = 20;
|
|
||||||
TypedBuffer<MyValue, amount> buffer;
|
|
||||||
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
{
|
|
||||||
MyValue value;
|
|
||||||
EXPECT_EQ(MyValue::alive, 1);
|
|
||||||
EXPECT_THROW(uninitialized_fill_n(buffer.ptr(), amount, value), std::exception);
|
|
||||||
EXPECT_EQ(MyValue::alive, 1);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(MyValue::alive, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestBaseClass {
|
class TestBaseClass {
|
||||||
virtual void mymethod(){};
|
virtual void mymethod(){};
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user