From: Pasukhin Dmitry Date: Mon, 24 Nov 2025 11:14:19 +0000 (+0000) Subject: Foundation Classes - Implement move semantics for math_Matrix and math_Vector (... X-Git-Url: http://git.dev.opencascade.org/gitweb/?a=commitdiff_plain;p=occt.git 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 --- diff --git a/src/FoundationClasses/TKMath/GTests/math_Matrix_Test.cxx b/src/FoundationClasses/TKMath/GTests/math_Matrix_Test.cxx index 12ca0728a8..39793242c3 100644 --- a/src/FoundationClasses/TKMath/GTests/math_Matrix_Test.cxx +++ b/src/FoundationClasses/TKMath/GTests/math_Matrix_Test.cxx @@ -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); +} diff --git a/src/FoundationClasses/TKMath/GTests/math_Vector_Test.cxx b/src/FoundationClasses/TKMath/GTests/math_Vector_Test.cxx index 6bdee08fcb..0881c96847 100644 --- a/src/FoundationClasses/TKMath/GTests/math_Vector_Test.cxx +++ b/src/FoundationClasses/TKMath/GTests/math_Vector_Test.cxx @@ -638,4 +638,66 @@ TEST(MathVectorTest, EdgeCases) EXPECT_EQ(aNegVec.Upper(), 1); EXPECT_EQ(aNegVec.Max(), 1); EXPECT_EQ(aNegVec.Min(), -2); -} \ No newline at end of file +} + +// 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(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(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(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(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); +} diff --git a/src/FoundationClasses/TKMath/math/math_DoubleTab.hxx b/src/FoundationClasses/TKMath/math/math_DoubleTab.hxx index c386b4d4e1..572f5d704f 100644 --- a/src/FoundationClasses/TKMath/math/math_DoubleTab.hxx +++ b/src/FoundationClasses/TKMath/math/math_DoubleTab.hxx @@ -26,6 +26,7 @@ #include #include +#include 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(*myBuffer.data(), + theOther.LowerRow(), + theOther.UpperRow(), + theOther.LowerCol(), + theOther.UpperCol()) + : NCollection_Array2(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; diff --git a/src/FoundationClasses/TKMath/math/math_Matrix.hxx b/src/FoundationClasses/TKMath/math/math_Matrix.hxx index ba1763ee48..713522da41 100644 --- a/src/FoundationClasses/TKMath/math/math_Matrix.hxx +++ b/src/FoundationClasses/TKMath/math/math_Matrix.hxx @@ -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. diff --git a/src/FoundationClasses/TKMath/math/math_Matrix.lxx b/src/FoundationClasses/TKMath/math/math_Matrix.lxx index 68e1638259..224d0ec0d7 100644 --- a/src/FoundationClasses/TKMath/math/math_Matrix.lxx +++ b/src/FoundationClasses/TKMath/math/math_Matrix.lxx @@ -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; } diff --git a/src/FoundationClasses/TKMath/math/math_VectorBase.hxx b/src/FoundationClasses/TKMath/math/math_VectorBase.hxx index 7445b95c95..a38afd1a73 100644 --- a/src/FoundationClasses/TKMath/math/math_VectorBase.hxx +++ b/src/FoundationClasses/TKMath/math/math_VectorBase.hxx @@ -27,6 +27,7 @@ #include #include +#include //! 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; diff --git a/src/FoundationClasses/TKMath/math/math_VectorBase.lxx b/src/FoundationClasses/TKMath/math/math_VectorBase.lxx index fbd83f37bf..6e0314c010 100644 --- a/src/FoundationClasses/TKMath/math/math_VectorBase.lxx +++ b/src/FoundationClasses/TKMath/math/math_VectorBase.lxx @@ -76,6 +76,23 @@ math_VectorBase::math_VectorBase(const math_VectorBase { } +template +math_VectorBase::math_VectorBase(math_VectorBase&& theOther) noexcept + : myBuffer{}, + Array(theOther.Array.IsDeletable() + ? std::move(theOther.Array) + : (theOther.Length() <= math_VectorBase::THE_BUFFER_SIZE + ? NCollection_Array1(*myBuffer.data(), + theOther.Lower(), + theOther.Upper()) + : NCollection_Array1(theOther.Lower(), theOther.Upper()))) +{ + if (!theOther.Array.IsEmpty()) + { + Array.Assign(theOther.Array); + } +} + template void math_VectorBase::SetLower(const Standard_Integer theLower) { @@ -543,6 +560,27 @@ math_VectorBase& math_VectorBase::Initialized( return *this; } +template +math_VectorBase& math_VectorBase::operator=( + math_VectorBase&& 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 void math_VectorBase::Dump(Standard_OStream& theO) const {