Skip to content

Commit

Permalink
PERF: Add fast noexcept "move semantics" to Array2D
Browse files Browse the repository at this point in the history
Added a `noexcept` move-constructor and move-assignment operator to `Array2D`,
which both _move_ the data of the `Array2D`.

Previously, an attempt to "move" an `Array2D` would have done an expensive copy.
  • Loading branch information
N-Dekker committed Jun 18, 2024
1 parent 328606b commit 5610e23
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 0 deletions.
20 changes: 20 additions & 0 deletions Modules/Core/Common/include/itkArray2D.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,33 @@ class ITK_TEMPLATE_EXPORT Array2D : public vnl_matrix<TValue>
/** Copy-constructor. */
Array2D(const Self & array);

/** Move-constructor.
* \note This move-constructor is `noexcept`, even while the move-constructor of its base class (`vnl_matrix`) is not
* `noexcept`, because unlike `vnl_matrix`, `Array2D` always manages its own memory. */
Array2D(Self && array) noexcept
: vnl_matrix<TValue>(std::move(array))
{
// Note: GCC <= 9.5 does not yet support "defaulting" (`= default`) this `noexcept` move-constructor.
}

/** Converting constructor. Implicitly converts the specified matrix to an Array2D. */
Array2D(const VnlMatrixType & matrix);

/** Copy-assignment operator. */
Self &
operator=(const Self & array);

/** Move-assignment operator.
* \note This move-assignment operator is `noexcept`, even while the move-assignment operator of its base class
* (`vnl_matrix`) is not `noexcept`, because unlike `vnl_matrix`, `Array2D` always manages its own memory. */
Self &
operator=(Self && array) noexcept
{
// Note: GCC <= 9.5 does not yet support "defaulting" (`= default`) this `noexcept` move-assignment operator.
this->VnlMatrixType::operator=(std::move(array));
return *this;
}

/** Assigns the specified matrix to an Array2D. */
Self &
operator=(const VnlMatrixType & matrix);
Expand Down
59 changes: 59 additions & 0 deletions Modules/Core/Common/test/itkArray2DGTest.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@
#include "itkArray2D.h"
#include <gtest/gtest.h>
#include <limits>
#include <type_traits> // For is_nothrow_move_constructible_v and is_nothrow_move_assignable_v.


static_assert(std::is_nothrow_move_constructible_v<itk::Array2D<int>> &&
std::is_nothrow_move_constructible_v<itk::Array2D<double>>,
"Array2D should have a `noexcept` move-constructor!");
static_assert(std::is_nothrow_move_assignable_v<itk::Array2D<int>> &&
std::is_nothrow_move_assignable_v<itk::Array2D<double>>,
"Array2D should have a `noexcept` move-assignment operator!");

// Tests that Array2D may be constructed with an initial value for each element.
TEST(Array2D, ConstructorSupportsInitialValue)
{
Expand Down Expand Up @@ -51,3 +59,54 @@ TEST(Array2D, ConstructorSupportsInitialValue)
checkConstructor(1, 2, initialValue);
}
}


// Tests that when move-constructing an Array2D, the data is "taken" from the original, and "moved" to the newly
// constructed object.
TEST(Array2D, MoveConstruct)
{
const auto checkMoveConstruct = [](auto && original) {
const auto * const * const originalDataArray{ original.data_array() };
const unsigned int originalSize{ original.size() };

const auto moveConstructed = std::move(original);

// After the "move", the move-constructed object has retrieved the original data.
EXPECT_EQ(moveConstructed.data_array(), originalDataArray);
EXPECT_EQ(moveConstructed.size(), originalSize);

// After the "move", the original is left empty.
EXPECT_EQ(original.data_array(), nullptr);
EXPECT_EQ(original.size(), 0U);
};

checkMoveConstruct(itk::Array2D<int>());
checkMoveConstruct(itk::Array2D<int>(1U, 1U));
checkMoveConstruct(itk::Array2D<double>(1U, 2U));
}


// Tests that when move-assigning an Array2D, the data is "taken" from the original, and "moved" to the target of the
// move-assignment.
TEST(Array2D, MoveAssign)
{
const auto checkMoveAssign = [](auto original) {
const auto * const * const originalDataArray{ original.data_array() };
const unsigned int originalSize{ original.size() };

decltype(original) moveAssigmentTarget;
moveAssigmentTarget = std::move(original);

// After the "move", the target of the move-assignment has retrieved the original data.
EXPECT_EQ(moveAssigmentTarget.data_array(), originalDataArray);
EXPECT_EQ(moveAssigmentTarget.size(), originalSize);

// After the "move", the original is left empty.
EXPECT_EQ(original.data_array(), nullptr);
EXPECT_EQ(original.size(), 0U);
};

checkMoveAssign(itk::Array2D<int>());
checkMoveAssign(itk::Array2D<int>(1U, 1U));
checkMoveAssign(itk::Array2D<double>(1U, 2U));
}

0 comments on commit 5610e23

Please sign in to comment.