Foundation Classes - Implement move semantics for math_Matrix and math_Vector (#841)

- Added move constructors and move assignment operators to `math_VectorBase`, `math_Matrix`, and `math_DoubleTab`
- Optimized move operations to avoid unnecessary copying when dimensions match and both objects use heap allocation
- Added comprehensive test coverage for move semantics with both heap-allocated (large) and buffer-allocated (small) objects
This commit is contained in:
Pasukhin Dmitry
2025-11-24 11:14:19 +00:00
committed by GitHub
parent 845e75e598
commit 6730c842bd
7 changed files with 234 additions and 5 deletions

View File

@@ -594,3 +594,56 @@ TEST(MathMatrixTest, InPlaceMatrixMultiplication)
EXPECT_NEAR(aMatrixACopy(2, 1), aExpectedAB(2, 1), Precision::Confusion());
EXPECT_NEAR(aMatrixACopy(2, 2), aExpectedAB(2, 2), Precision::Confusion());
}
// Tests for Move Semantics
TEST(MathMatrixTest, MoveSemantics)
{
// --- Move Constructor ---
// Large matrix (heap allocated)
Standard_Integer aRows = 10;
Standard_Integer aCols = 10;
math_Matrix aMat1(1, aRows, 1, aCols);
aMat1.Init(1.0);
aMat1(1, 1) = 2.0;
// Move aMat1 to aMat2
math_Matrix aMat2(std::move(aMat1));
EXPECT_EQ(aMat2.RowNumber(), aRows);
EXPECT_EQ(aMat2.ColNumber(), aCols);
EXPECT_EQ(aMat2(1, 1), 2.0);
// Verify source state (should be empty after move)
EXPECT_EQ(aMat1.RowNumber(), 0);
// Small matrix (buffer allocated)
Standard_Integer aSmallRows = 4;
Standard_Integer aSmallCols = 4;
math_Matrix aSmallMat1(1, aSmallRows, 1, aSmallCols);
aSmallMat1.Init(1.0);
// Move aSmallMat1 to aSmallMat2 (should copy because of buffer)
math_Matrix aSmallMat2(std::move(aSmallMat1));
EXPECT_EQ(aSmallMat2.RowNumber(), aSmallRows);
EXPECT_EQ(aSmallMat2(1, 1), 1.0);
// Source remains valid for buffer-based matrix
EXPECT_EQ(aSmallMat1.RowNumber(), aSmallRows);
EXPECT_EQ(aSmallMat1(1, 1), 1.0);
// --- Move Assignment ---
// Large matrix move assignment
math_Matrix aMatAssign1(1, aRows, 1, aCols);
aMatAssign1.Init(5.0);
math_Matrix aMatAssign2(1, aRows, 1, aCols);
aMatAssign2.Init(0.0);
aMatAssign2 = std::move(aMatAssign1);
EXPECT_EQ(aMatAssign2(1, 1), 5.0);
EXPECT_EQ(aMatAssign1.RowNumber(), 0);
}

View File

@@ -638,4 +638,66 @@ TEST(MathVectorTest, EdgeCases)
EXPECT_EQ(aNegVec.Upper(), 1);
EXPECT_EQ(aNegVec.Max(), 1);
EXPECT_EQ(aNegVec.Min(), -2);
}
}
// Tests for Move Semantics
TEST(MathVectorTest, MoveSemantics)
{
// --- Move Constructor ---
// Large vector (heap allocated)
Standard_Integer aLen = 100;
math_Vector aVec1(1, aLen);
for (Standard_Integer i = 1; i <= aLen; ++i)
{
aVec1(i) = static_cast<Standard_Real>(i);
}
// Move aVec1 to aVec2
math_Vector aVec2(std::move(aVec1));
EXPECT_EQ(aVec2.Length(), aLen);
EXPECT_EQ(aVec2(1), 1.0);
EXPECT_EQ(aVec2(aLen), static_cast<Standard_Real>(aLen));
// Verify source state (length should be 0 after move for NCollection_Array1)
// Note: calling Length() is safe as it just returns size.
EXPECT_EQ(aVec1.Length(), 0);
// Small vector (buffer allocated)
Standard_Integer aSmallLen = 10;
math_Vector aSmallVec1(1, aSmallLen);
for (Standard_Integer i = 1; i <= aSmallLen; ++i)
{
aSmallVec1(i) = static_cast<Standard_Real>(i);
}
// Move aSmallVec1 to aSmallVec2 (should copy because of buffer)
math_Vector aSmallVec2(std::move(aSmallVec1));
EXPECT_EQ(aSmallVec2.Length(), aSmallLen);
EXPECT_EQ(aSmallVec2(1), 1.0);
// Source remains valid for buffer-based vector
EXPECT_EQ(aSmallVec1.Length(), aSmallLen);
EXPECT_EQ(aSmallVec1(1), 1.0);
// --- Move Assignment ---
// Large vector move assignment
math_Vector aVecAssign1(1, aLen);
for (Standard_Integer i = 1; i <= aLen; ++i)
{
aVecAssign1(i) = static_cast<Standard_Real>(i);
}
math_Vector aVecAssign2(1, aLen);
aVecAssign2.Init(0.0);
aVecAssign2 = std::move(aVecAssign1);
EXPECT_EQ(aVecAssign2.Length(), aLen);
EXPECT_EQ(aVecAssign2(1), 1.0);
EXPECT_EQ(aVecAssign1.Length(), 0);
}

View File

@@ -26,6 +26,7 @@
#include <Standard_Boolean.hxx>
#include <array>
#include <utility>
class math_DoubleTab
{
@@ -77,9 +78,34 @@ public:
{
}
//! Move constructor
math_DoubleTab(math_DoubleTab&& theOther) noexcept
: myBuffer{},
myArray(theOther.myArray.IsDeletable()
? std::move(theOther.myArray)
: (theOther.NbRows() * theOther.NbColumns() <= THE_BUFFER_SIZE
? NCollection_Array2<Standard_Real>(*myBuffer.data(),
theOther.LowerRow(),
theOther.UpperRow(),
theOther.LowerCol(),
theOther.UpperCol())
: NCollection_Array2<Standard_Real>(theOther.LowerRow(),
theOther.UpperRow(),
theOther.LowerCol(),
theOther.UpperCol())))
{
if (!theOther.myArray.IsEmpty())
{
myArray.Assign(theOther.myArray);
}
}
//! Copy data to theOther
void Copy(math_DoubleTab& theOther) const { theOther.myArray.Assign(myArray); }
//! Returns true if the internal array is deletable (heap-allocated)
Standard_Boolean IsDeletable() const { return myArray.IsDeletable(); }
//! Set lower row index
void SetLowerRow(const Standard_Integer theLowerRow) { myArray.UpdateLowerRow(theLowerRow); }
@@ -130,6 +156,39 @@ public:
return Value(theRowIndex, theColIndex);
}
//! Assignment operator
math_DoubleTab& operator=(const math_DoubleTab& theOther)
{
if (this != &theOther)
{
myArray = theOther.myArray;
}
return *this;
}
//! Move assignment operator
math_DoubleTab& operator=(math_DoubleTab&& theOther) noexcept
{
if (this == &theOther)
{
return *this;
}
if (myArray.IsDeletable() && theOther.myArray.IsDeletable()
&& myArray.NbRows() == theOther.myArray.NbRows()
&& myArray.NbColumns() == theOther.myArray.NbColumns()
&& myArray.LowerRow() == theOther.myArray.LowerRow()
&& myArray.LowerCol() == theOther.myArray.LowerCol())
{
myArray.Move(theOther.myArray);
}
else
{
myArray = theOther.myArray;
}
return *this;
}
//! Destructor
~math_DoubleTab() = default;

View File

@@ -379,7 +379,7 @@ public:
math_Matrix& operator=(const math_Matrix& Other) { return Initialized(Other); }
//! Move assignment operator
inline math_Matrix& operator=(math_Matrix&& Other) noexcept;
inline math_Matrix& operator=(math_Matrix&& Other);
//! Returns the product of 2 matrices.
//! An exception is raised if the dimensions are different.

View File

@@ -701,12 +701,23 @@ inline math_Matrix& math_Matrix::Initialized(const math_Matrix& Other)
//==================================================================================================
inline math_Matrix& math_Matrix::operator=(math_Matrix&& Other) noexcept
inline math_Matrix& math_Matrix::operator=(math_Matrix&& Other)
{
if (this != &Other)
if (this == &Other)
{
return *this;
}
if (Array.IsDeletable() && Other.Array.IsDeletable() && RowNumber() == Other.RowNumber()
&& ColNumber() == Other.ColNumber() && LowerRow() == Other.LowerRow()
&& LowerCol() == Other.LowerCol())
{
Array = std::move(Other.Array);
}
else
{
Initialized(Other);
}
return *this;
}

View File

@@ -27,6 +27,7 @@
#include <math_Matrix.hxx>
#include <array>
#include <utility>
//! This class implements the real vector abstract data type.
//! Vectors can have an arbitrary range which must be defined at
@@ -94,9 +95,11 @@ public:
void Init(const TheItemType theInitialValue);
//! Constructs a copy for initialization.
//! An exception is raised if the lengths of the vectors are different.
inline math_VectorBase(const math_VectorBase& theOther);
//! Move constructor
inline math_VectorBase(math_VectorBase&& theOther) noexcept;
//! Returns the length of a vector
inline Standard_Integer Length() const { return Array.Length(); }
@@ -255,6 +258,9 @@ public:
math_VectorBase& operator=(const math_VectorBase& theOther) { return Initialized(theOther); }
//! Move assignment operator
inline math_VectorBase& operator=(math_VectorBase&& theOther);
//! returns the inner product of 2 vectors.
//! An exception is raised if the lengths are not equal.
Standard_NODISCARD inline TheItemType Multiplied(const math_VectorBase& theRight) const;

View File

@@ -76,6 +76,23 @@ math_VectorBase<TheItemType>::math_VectorBase(const math_VectorBase<TheItemType>
{
}
template <typename TheItemType>
math_VectorBase<TheItemType>::math_VectorBase(math_VectorBase<TheItemType>&& theOther) noexcept
: myBuffer{},
Array(theOther.Array.IsDeletable()
? std::move(theOther.Array)
: (theOther.Length() <= math_VectorBase::THE_BUFFER_SIZE
? NCollection_Array1<TheItemType>(*myBuffer.data(),
theOther.Lower(),
theOther.Upper())
: NCollection_Array1<TheItemType>(theOther.Lower(), theOther.Upper())))
{
if (!theOther.Array.IsEmpty())
{
Array.Assign(theOther.Array);
}
}
template <typename TheItemType>
void math_VectorBase<TheItemType>::SetLower(const Standard_Integer theLower)
{
@@ -543,6 +560,27 @@ math_VectorBase<TheItemType>& math_VectorBase<TheItemType>::Initialized(
return *this;
}
template <typename TheItemType>
math_VectorBase<TheItemType>& math_VectorBase<TheItemType>::operator=(
math_VectorBase<TheItemType>&& theOther)
{
if (this == &theOther)
{
return *this;
}
if (Array.IsDeletable() && theOther.Array.IsDeletable() && Lower() == theOther.Lower()
&& Length() == theOther.Length())
{
Array.Move(theOther.Array);
}
else
{
Initialized(theOther);
}
return *this;
}
template <typename TheItemType>
void math_VectorBase<TheItemType>::Dump(Standard_OStream& theO) const
{