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 defaulted `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 63658e8
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Modules/Core/Common/include/itkArray2D.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,24 @@ 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 &&) noexcept = default;

/** 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 &&) noexcept = default;

/** Assigns the specified matrix to an Array2D. */
Self &
operator=(const VnlMatrixType & matrix);
Expand Down
56 changes: 56 additions & 0 deletions Modules/Core/Common/test/itkArray2DGTest.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
#include <limits>


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 +58,52 @@ 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>(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>(1U, 1U));
checkMoveAssign(itk::Array2D<double>(1U, 2U));
}

0 comments on commit 63658e8

Please sign in to comment.