diff --git a/src/FoundationClasses/TKernel/GTests/NCollection_Array1_Test.cxx b/src/FoundationClasses/TKernel/GTests/NCollection_Array1_Test.cxx index 1ada535c6c..8a33dd8692 100644 --- a/src/FoundationClasses/TKernel/GTests/NCollection_Array1_Test.cxx +++ b/src/FoundationClasses/TKernel/GTests/NCollection_Array1_Test.cxx @@ -475,3 +475,175 @@ TEST(NCollection_Array1Test, EmplaceValue_ReplacesExisting) EXPECT_EQ(200, anArray(2).myA); EXPECT_EQ(30, anArray(3).myA); } + +// ============================================================================ +// Zero-based (size_t) construction mode tests +// ============================================================================ + +TEST(NCollection_Array1Test, SizeConstructor_AllocatesCorrectly) +{ + const size_t aSize = 10; + NCollection_Array1 anArray(aSize); + + EXPECT_EQ(aSize, anArray.Size()); + EXPECT_EQ(0, anArray.Lower()); + EXPECT_EQ(static_cast(aSize) - 1, anArray.Upper()); + EXPECT_FALSE(anArray.IsEmpty()); + EXPECT_TRUE(anArray.IsDeletable()); +} + +TEST(NCollection_Array1Test, SizeConstructor_ZeroSize) +{ + NCollection_Array1 anArray(static_cast(0)); + + EXPECT_EQ(0u, anArray.Size()); + EXPECT_TRUE(anArray.IsEmpty()); +} + +TEST(NCollection_Array1Test, SizeConstructor_AtAccess) +{ + const size_t aSize = 5; + NCollection_Array1 anArray(aSize); + + for (size_t i = 0; i < aSize; ++i) + { + anArray.ChangeAt(i) = static_cast(i * 10); + } + + for (size_t i = 0; i < aSize; ++i) + { + EXPECT_EQ(static_cast(i * 10), anArray.At(i)); + } +} + +TEST(NCollection_Array1Test, SizeConstructor_IteratorAccess) +{ + const size_t aSize = 6; + NCollection_Array1 anArray(aSize); + + int aFill = 0; + for (auto& aVal : anArray) + { + aVal = aFill++; + } + + int aCheck = 0; + for (const auto& aVal : anArray) + { + EXPECT_EQ(aCheck++, aVal); + } + EXPECT_EQ(static_cast(aSize), aCheck); +} + +TEST(NCollection_Array1Test, SizeConstructor_LegacyValueOperator) +{ + // When lower==0, operator[](i) should also work 0-based + const size_t aSize = 4; + NCollection_Array1 anArray(aSize); + for (size_t i = 0; i < aSize; ++i) + { + anArray[static_cast(i)] = static_cast(i + 100); + } + for (size_t i = 0; i < aSize; ++i) + { + EXPECT_EQ(static_cast(i + 100), anArray[static_cast(i)]); + } +} + +TEST(NCollection_Array1Test, SizeConstructor_BufferReuse_NotOwner) +{ + int aBuf[8] = {10, 20, 30, 40, 50, 60, 70, 80}; + NCollection_Array1 anArray(aBuf, 8); + + EXPECT_FALSE(anArray.IsDeletable()); + EXPECT_EQ(8u, anArray.Size()); + EXPECT_EQ(0, anArray.Lower()); + EXPECT_EQ(7, anArray.Upper()); +} + +TEST(NCollection_Array1Test, SizeConstructor_BufferReuse_DataPreserved) +{ + int aBuf[5] = {1, 2, 3, 4, 5}; + NCollection_Array1 anArray(aBuf, 5); + + for (size_t i = 0; i < 5; ++i) + { + EXPECT_EQ(static_cast(i + 1), anArray.At(i)); + } +} + +TEST(NCollection_Array1Test, SizeConstructor_BufferReuse_WritesGoToBuffer) +{ + int aBuf[4] = {0, 0, 0, 0}; + { + NCollection_Array1 anArray(aBuf, 4); + for (size_t i = 0; i < 4; ++i) + { + anArray.ChangeAt(i) = static_cast(i + 7); + } + } + // After array goes out of scope, buffer should still hold written values + for (int i = 0; i < 4; ++i) + { + EXPECT_EQ(i + 7, aBuf[i]); + } +} + +TEST(NCollection_Array1Test, SizeConstructor_Resize_Grow) +{ + NCollection_Array1 anArray(static_cast(3)); + anArray.ChangeAt(0) = 10; + anArray.ChangeAt(1) = 20; + anArray.ChangeAt(2) = 30; + + anArray.Resize(static_cast(5), true); + + EXPECT_EQ(5u, anArray.Size()); + EXPECT_EQ(10, anArray.At(0)); + EXPECT_EQ(20, anArray.At(1)); + EXPECT_EQ(30, anArray.At(2)); +} + +TEST(NCollection_Array1Test, SizeConstructor_Resize_Shrink) +{ + NCollection_Array1 anArray(static_cast(5)); + for (size_t i = 0; i < 5; ++i) + { + anArray.ChangeAt(i) = static_cast(i); + } + + anArray.Resize(static_cast(3), true); + + EXPECT_EQ(3u, anArray.Size()); + EXPECT_EQ(0, anArray.At(0)); + EXPECT_EQ(1, anArray.At(1)); + EXPECT_EQ(2, anArray.At(2)); +} + +TEST(NCollection_Array1Test, SizeConstructor_Resize_NoData) +{ + NCollection_Array1 anArray(static_cast(4)); + anArray.ChangeAt(0) = 99; + + anArray.Resize(static_cast(6), false); + + EXPECT_EQ(6u, anArray.Size()); + EXPECT_TRUE(anArray.IsDeletable()); +} + +TEST(NCollection_Array1Test, SizeConstructor_MoveSemantics) +{ + NCollection_Array1 anSrc(static_cast(3)); + anSrc.ChangeAt(0) = 1; + anSrc.ChangeAt(1) = 2; + anSrc.ChangeAt(2) = 3; + + NCollection_Array1 anDst(std::move(anSrc)); + + EXPECT_EQ(3u, anDst.Size()); + EXPECT_EQ(1, anDst.At(0)); + EXPECT_EQ(2, anDst.At(1)); + EXPECT_EQ(3, anDst.At(2)); + EXPECT_EQ(0u, anSrc.Size()); + EXPECT_TRUE(anSrc.IsEmpty()); +} diff --git a/src/FoundationClasses/TKernel/GTests/NCollection_Array2_Test.cxx b/src/FoundationClasses/TKernel/GTests/NCollection_Array2_Test.cxx index 323924eaae..a28be1b1bc 100644 --- a/src/FoundationClasses/TKernel/GTests/NCollection_Array2_Test.cxx +++ b/src/FoundationClasses/TKernel/GTests/NCollection_Array2_Test.cxx @@ -629,4 +629,133 @@ TEST(NCollection_Array2Test, ResizeWithTrim_GrowPreservesOldRegion) EXPECT_EQ(99, anArray(aRow, aCol)); } } +} + +// ============================================================================ +// Zero-based (size_t) construction mode tests +// ============================================================================ + +TEST(NCollection_Array2Test, SizeConstructor_AllocatesCorrectly) +{ + const size_t aNbRows = 3; + const size_t aNbCols = 4; + NCollection_Array2 anArray(aNbRows, aNbCols); + + EXPECT_EQ(aNbRows, static_cast(anArray.NbRows())); + EXPECT_EQ(aNbCols, static_cast(anArray.NbColumns())); + EXPECT_EQ(aNbRows * aNbCols, anArray.Size()); + EXPECT_EQ(0, anArray.LowerRow()); + EXPECT_EQ(0, anArray.LowerCol()); + EXPECT_EQ(static_cast(aNbRows) - 1, anArray.UpperRow()); + EXPECT_EQ(static_cast(aNbCols) - 1, anArray.UpperCol()); + EXPECT_TRUE(anArray.IsDeletable()); +} + +TEST(NCollection_Array2Test, SizeConstructor_AtAccess) +{ + const size_t aNbRows = 3; + const size_t aNbCols = 5; + NCollection_Array2 anArray(aNbRows, aNbCols); + + for (size_t aRow = 0; aRow < aNbRows; ++aRow) + { + for (size_t aCol = 0; aCol < aNbCols; ++aCol) + { + anArray.ChangeAt(aRow, aCol) = static_cast(aRow * 100 + aCol); + } + } + + for (size_t aRow = 0; aRow < aNbRows; ++aRow) + { + for (size_t aCol = 0; aCol < aNbCols; ++aCol) + { + EXPECT_EQ(static_cast(aRow * 100 + aCol), anArray.At(aRow, aCol)); + } + } +} + +TEST(NCollection_Array2Test, SizeConstructor_LegacyOperatorZeroBased) +{ + // When LowerRow/Col == 0, operator()(row, col) is also 0-based + NCollection_Array2 anArray(static_cast(2), static_cast(3)); + anArray(0, 0) = 1; + anArray(0, 1) = 2; + anArray(1, 2) = 9; + + EXPECT_EQ(1, anArray.At(0, 0)); + EXPECT_EQ(2, anArray.At(0, 1)); + EXPECT_EQ(9, anArray.At(1, 2)); +} + +TEST(NCollection_Array2Test, SizeConstructor_BufferReuse_NotOwner) +{ + int aBuf[6] = {1, 2, 3, 4, 5, 6}; + NCollection_Array2 anArray(aBuf, 2, 3); + + EXPECT_FALSE(anArray.IsDeletable()); + EXPECT_EQ(2, anArray.NbRows()); + EXPECT_EQ(3, anArray.NbColumns()); + EXPECT_EQ(0, anArray.LowerRow()); + EXPECT_EQ(0, anArray.LowerCol()); +} + +TEST(NCollection_Array2Test, SizeConstructor_BufferReuse_DataPreserved) +{ + // Row-major layout: buf[row * nbCols + col] + int aBuf[6] = {10, 20, 30, 40, 50, 60}; + NCollection_Array2 anArray(aBuf, 2, 3); + + EXPECT_EQ(10, anArray.At(0, 0)); + EXPECT_EQ(20, anArray.At(0, 1)); + EXPECT_EQ(30, anArray.At(0, 2)); + EXPECT_EQ(40, anArray.At(1, 0)); + EXPECT_EQ(50, anArray.At(1, 1)); + EXPECT_EQ(60, anArray.At(1, 2)); +} + +TEST(NCollection_Array2Test, SizeConstructor_Resize_Grow) +{ + NCollection_Array2 anArray(static_cast(2), static_cast(2)); + anArray.Init(7); + + anArray.Resize(static_cast(3), static_cast(3), true); + + EXPECT_EQ(3, anArray.NbRows()); + EXPECT_EQ(3, anArray.NbColumns()); + // Original 2x2 values (linear copy) should be preserved in first 4 elements + EXPECT_EQ(7, anArray.At(0, 0)); + EXPECT_EQ(7, anArray.At(0, 1)); +} + +TEST(NCollection_Array2Test, SizeConstructor_Resize_NoData) +{ + NCollection_Array2 anArray(static_cast(3), static_cast(4)); + anArray.Init(5); + + anArray.Resize(static_cast(2), static_cast(2), false); + + EXPECT_EQ(2, anArray.NbRows()); + EXPECT_EQ(2, anArray.NbColumns()); + EXPECT_EQ(0, anArray.LowerRow()); + EXPECT_EQ(0, anArray.LowerCol()); + EXPECT_TRUE(anArray.IsDeletable()); +} + +TEST(NCollection_Array2Test, SizeConstructor_ResizeWithTrim_GrowPreserves) +{ + NCollection_Array2 anArray(static_cast(2), static_cast(2)); + anArray.Init(42); + + anArray.ResizeWithTrim(static_cast(3), static_cast(3), true); + + EXPECT_EQ(3, anArray.NbRows()); + EXPECT_EQ(3, anArray.NbColumns()); + // Original 2x2 corner should be preserved + for (size_t aRow = 0; aRow < 2; ++aRow) + { + for (size_t aCol = 0; aCol < 2; ++aCol) + { + EXPECT_EQ(42, anArray.At(aRow, aCol)); + } + } } \ No newline at end of file diff --git a/src/FoundationClasses/TKernel/NCollection/NCollection_Array1.hxx b/src/FoundationClasses/TKernel/NCollection/NCollection_Array1.hxx index 7952fffe89..86c014e611 100644 --- a/src/FoundationClasses/TKernel/NCollection/NCollection_Array1.hxx +++ b/src/FoundationClasses/TKernel/NCollection/NCollection_Array1.hxx @@ -49,6 +49,19 @@ //! @code //! for (i = A.Lower(); i <= A.Upper(); i++) //! @endcode +//! +//! Zero-based (size_t) construction mode: +//! Use NCollection_Array1(size_t theSize) or NCollection_Array1(pointer, size_t) to create +//! a zero-based array (Lower()==0). In this mode At()/ChangeAt() and STL iterators are the +//! preferred access path - they address elements directly without any offset subtraction. +//! Buffer-reuse variants do NOT own the memory and will not free it on destruction. +//! @code +//! int aBuffer[100]; +//! NCollection_Array1 aZero(100); // allocates, lower=0 +//! NCollection_Array1 aWrap(aBuffer, 100); // wraps aBuffer, lower=0, not owner +//! for (size_t i = 0; i < aWrap.Size(); ++i) +//! aWrap.At(i) = static_cast(i); +//! @endcode template class NCollection_Array1 { @@ -114,24 +127,6 @@ public: construct(0, mySize); } - explicit NCollection_Array1(const allocator_type& theAlloc, - const int theLower, - const int theUpper) - : myLowerBound(theLower), - mySize(theUpper - theLower + 1), - myPointer(nullptr), - myIsOwner(false), - myAllocator(theAlloc) - { - if (mySize == 0) - { - return; - } - myPointer = myAllocator.allocate(mySize); - myIsOwner = true; - construct(0, mySize); - } - explicit NCollection_Array1(const_reference theBegin, const int theLower, const int theUpper, @@ -150,6 +145,32 @@ public: construct(0, mySize); } + //! Zero-based constructor: allocates theSize elements with lower bound 0. + //! Use At()/ChangeAt() or STL iterators for optimal access (no offset subtraction). + explicit NCollection_Array1(const size_t theSize) + : myLowerBound(0), + mySize(theSize) + { + if (mySize == 0) + { + return; + } + myPointer = myAllocator.allocate(mySize); + myIsOwner = true; + construct(0, mySize); + } + + //! Zero-based buffer-reuse constructor: wraps an existing C array of theSize elements. + //! The array does NOT own the buffer and will NOT free it on destruction. + //! Use At()/ChangeAt() or STL iterators for optimal access (no offset subtraction). + explicit NCollection_Array1(pointer theBegin, const size_t theSize) + : myLowerBound(0), + mySize(theSize), + myPointer(theBegin), + myIsOwner(false) + { + } + //! Copy constructor NCollection_Array1(const NCollection_Array1& theOther) : myLowerBound(theOther.myLowerBound), @@ -364,44 +385,15 @@ public: void Resize(const int theLower, const int theUpper, const bool theToCopyData) { Standard_RangeError_Raise_if(theUpper < theLower, "NCollection_Array1::Resize"); - const size_t aNewSize = static_cast(theUpper - theLower + 1); - pointer aPrevContPnt = myPointer; - if (aNewSize == mySize) - { - myLowerBound = theLower; - return; - } - if (myIsOwner) - { - if (theToCopyData) - destroy(myPointer, aNewSize, mySize); - else - destroy(myPointer, 0, mySize); - } - myLowerBound = theLower; - if (theToCopyData) - { - const size_t aMinSize = (std::min)(aNewSize, mySize); - if (myIsOwner) - { - myPointer = myAllocator.reallocate(myPointer, aNewSize); - } - else - { - myPointer = myAllocator.allocate(aNewSize); - copyConstruct(aPrevContPnt, aMinSize); - } - construct(mySize, aNewSize); - } - else - { - if (myIsOwner) - myAllocator.deallocate(aPrevContPnt, mySize); - myPointer = myAllocator.allocate(aNewSize); - construct(0, aNewSize); - } - mySize = aNewSize; - myIsOwner = true; + resizeImpl(static_cast(theUpper - theLower + 1), theLower, theToCopyData); + } + + //! Resizes the array to theSize elements, keeping the lower bound unchanged. + //! @param theSize new number of elements + //! @param theToCopyData flag to copy existing data into new array + void Resize(const size_t theSize, const bool theToCopyData) + { + resizeImpl(theSize, myLowerBound, theToCopyData); } bool IsDeletable() const noexcept { return myIsOwner; } @@ -409,6 +401,52 @@ public: friend iterator; friend const_iterator; +protected: + //! Core resize implementation used by all public Resize() overloads. + //! @param theNewSize new number of elements + //! @param theNewLower new lower bound value to store + //! @param theToCopyData whether to preserve existing elements + void resizeImpl(const size_t theNewSize, const int theNewLower, const bool theToCopyData) + { + const pointer aPrevPtr = myPointer; + if (theNewSize == mySize) + { + myLowerBound = theNewLower; + return; + } + if (myIsOwner) + { + if (theToCopyData) + destroy(myPointer, theNewSize, mySize); + else + destroy(myPointer, 0, mySize); + } + myLowerBound = theNewLower; + if (theToCopyData) + { + const size_t aMinSize = (std::min)(theNewSize, mySize); + if (myIsOwner) + { + myPointer = myAllocator.reallocate(myPointer, theNewSize); + } + else + { + myPointer = myAllocator.allocate(theNewSize); + copyConstruct(aPrevPtr, aMinSize); + } + construct(mySize < theNewSize ? mySize : theNewSize, theNewSize); + } + else + { + if (myIsOwner) + myAllocator.deallocate(aPrevPtr, mySize); + myPointer = myAllocator.allocate(theNewSize); + construct(0, theNewSize); + } + mySize = theNewSize; + myIsOwner = true; + } + protected: const_reference at(const size_t theIndex) const { @@ -422,7 +460,6 @@ protected: return myPointer[theIndex]; } -protected: template typename std::enable_if::value, void>::type construct( const size_t, diff --git a/src/FoundationClasses/TKernel/NCollection/NCollection_Array2.hxx b/src/FoundationClasses/TKernel/NCollection/NCollection_Array2.hxx index 06948ac3fe..a24709135b 100644 --- a/src/FoundationClasses/TKernel/NCollection/NCollection_Array2.hxx +++ b/src/FoundationClasses/TKernel/NCollection/NCollection_Array2.hxx @@ -39,6 +39,13 @@ * * for (i = A.LowerRow(); i <= A.UpperRow(); i++) * for (j = A.LowerCol(); j <= A.UpperCol(); j++) + * + * Zero-based (size_t) construction mode: + * NCollection_Array2(size_t theNbRows, size_t theNbCols) creates a zero-based array + * (LowerRow()==0, LowerCol()==0). In this mode At()/ChangeAt() and STL iterators are + * the preferred access path -- they address elements directly without any offset subtraction. + * Buffer-reuse variant NCollection_Array2(pointer, size_t, size_t) wraps an existing + * flat row-major buffer and does NOT own the memory. */ template class NCollection_Array2 : public NCollection_Array1 @@ -111,23 +118,6 @@ public: { } - //! Constructor - explicit NCollection_Array2(const allocator_type& theAlloc, - const int theRowLower, - const int theRowUpper, - const int theColLower, - const int theColUpper) - : NCollection_Array1( - theAlloc, - BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper), - LastPosition(theRowLower, theRowUpper, theColLower, theColUpper)), - myLowerRow(theRowLower), - mySizeRow(theRowUpper - theRowLower + 1), - myLowerCol(theColLower), - mySizeCol(theColUpper - theColLower + 1) - { - } - //! Copy constructor NCollection_Array2(const NCollection_Array2& theOther) : NCollection_Array1(theOther), @@ -169,6 +159,29 @@ public: { } + //! Zero-based constructor: allocates theNbRows x theNbCols elements with lower bounds 0. + //! Use At()/ChangeAt() or STL iterators for optimal access (no offset subtraction). + explicit NCollection_Array2(const size_t theNbRows, const size_t theNbCols) + : NCollection_Array1(theNbRows * theNbCols), + myLowerRow(0), + mySizeRow(theNbRows), + myLowerCol(0), + mySizeCol(theNbCols) + { + } + + //! Zero-based buffer-reuse constructor: wraps an existing flat row-major C array. + //! The array does NOT own the buffer and will NOT free it on destruction. + //! Use At()/ChangeAt() or STL iterators for optimal access (no offset subtraction). + explicit NCollection_Array2(pointer theBegin, const size_t theNbRows, const size_t theNbCols) + : NCollection_Array1(theBegin, theNbRows * theNbCols), + myLowerRow(0), + mySizeRow(theNbRows), + myLowerCol(0), + mySizeCol(theNbCols) + { + } + //! Size (number of items). size_t Size() const noexcept { return mySizeRow * mySizeCol; } @@ -387,6 +400,34 @@ public: resizeImpl(theRowLower, theRowUpper, theColLower, theColUpper); } + //! Zero-based Resize: resizes to theNbRows x theNbCols, keeping lower bounds unchanged. + //! No re-allocation is done if dimensions are unchanged. + //! @param theNbRows new number of rows + //! @param theNbCols new number of columns + //! @param theToCopyData flag to copy existing data into new array + void Resize(const size_t theNbRows, const size_t theNbCols, const bool theToCopyData) + { + Resize(myLowerRow, + myLowerRow + static_cast(theNbRows) - 1, + myLowerCol, + myLowerCol + static_cast(theNbCols) - 1, + theToCopyData); + } + + //! Zero-based ResizeWithTrim: resizes preserving 2D layout, keeping lower bounds unchanged. + //! No re-allocation is done if dimensions are unchanged. + //! @param theNbRows new number of rows + //! @param theNbCols new number of columns + //! @param theToCopyData flag to copy existing data into new array + void ResizeWithTrim(const size_t theNbRows, const size_t theNbCols, const bool theToCopyData) + { + ResizeWithTrim(myLowerRow, + myLowerRow + static_cast(theNbRows) - 1, + myLowerCol, + myLowerCol + static_cast(theNbCols) - 1, + theToCopyData); + } + protected: //! Resize without copying data. void resizeNoData(int theRowLower, int theRowUpper, int theColLower, int theColUpper)