[IntervalMap] Add move and copy ctors and assignment operators

And update the unittest.

Reviewed By: dblaikie

Differential Revision: https://reviews.llvm.org/D136242
This commit is contained in:
OCHyams 2022-10-27 08:52:47 +01:00
parent e8541e4b42
commit dbbe82c6fd
2 changed files with 120 additions and 43 deletions

View File

@ -975,13 +975,13 @@ private:
// 0: Leaves in root. // 0: Leaves in root.
// 1: Root points to leaf. // 1: Root points to leaf.
// 2: root->branch->leaf ... // 2: root->branch->leaf ...
unsigned height; unsigned height = 0;
// Number of entries in the root node. // Number of entries in the root node.
unsigned rootSize; unsigned rootSize = 0;
// Allocator used for creating external nodes. // Allocator used for creating external nodes.
Allocator &allocator; Allocator *allocator = nullptr;
const RootLeaf &rootLeaf() const { const RootLeaf &rootLeaf() const {
assert(!branched() && "Cannot acces leaf data in branched root"); assert(!branched() && "Cannot acces leaf data in branched root");
@ -1007,12 +1007,12 @@ private:
KeyT &rootBranchStart() { return rootBranchData().start; } KeyT &rootBranchStart() { return rootBranchData().start; }
template <typename NodeT> NodeT *newNode() { template <typename NodeT> NodeT *newNode() {
return new(allocator.template Allocate<NodeT>()) NodeT(); return new (allocator->template Allocate<NodeT>()) NodeT();
} }
template <typename NodeT> void deleteNode(NodeT *P) { template <typename NodeT> void deleteNode(NodeT *P) {
P->~NodeT(); P->~NodeT();
allocator.Deallocate(P); allocator->Deallocate(P);
} }
IdxPair branchRoot(unsigned Position); IdxPair branchRoot(unsigned Position);
@ -1038,20 +1038,59 @@ private:
void deleteNode(IntervalMapImpl::NodeRef Node, unsigned Level); void deleteNode(IntervalMapImpl::NodeRef Node, unsigned Level);
public: public:
explicit IntervalMap(Allocator &a) : height(0), rootSize(0), allocator(a) { explicit IntervalMap(Allocator &a) : allocator(&a) {
new(&rootLeaf()) RootLeaf(); new (&rootLeaf()) RootLeaf();
} }
// The default copy/move constructors and assignment operators would perform ///@{
// a shallow copy, leading to an incorrect internal state. To prevent /// NOTE: The moved-from or copied-from object's allocator needs to have a
// accidental use, explicitly delete these operators. /// lifetime equal to or exceeding the moved-to or copied-to object to avoid
// If necessary, implement them to perform a deep copy. /// undefined behaviour.
IntervalMap(const IntervalMap &Other) = delete; IntervalMap(IntervalMap const &RHS) : IntervalMap(*RHS.allocator) {
IntervalMap(IntervalMap &&Other) = delete; // Future-proofing assertion: this function assumes the IntervalMap
// Note: these are already implicitly deleted, because RootLeaf (union // constructor doesn't add any nodes.
// member) has a non-trivial assignment operator (because of std::pair). assert(empty() && "Expected emptry tree");
IntervalMap &operator=(const IntervalMap &Other) = delete; *this = RHS;
IntervalMap &operator=(IntervalMap &&Other) = delete; }
IntervalMap &operator=(IntervalMap const &RHS) {
clear();
allocator = RHS.allocator;
for (auto It = RHS.begin(), End = RHS.end(); It != End; ++It)
insert(It.start(), It.stop(), It.value());
return *this;
}
IntervalMap(IntervalMap &&RHS) : IntervalMap(*RHS.allocator) {
// Future-proofing assertion: this function assumes the IntervalMap
// constructor doesn't add any nodes.
assert(empty() && "Expected emptry tree");
*this = std::move(RHS);
}
IntervalMap &operator=(IntervalMap &&RHS) {
// Calling clear deallocates memory and switches to rootLeaf.
clear();
// Destroy the new rootLeaf.
rootLeaf().~RootLeaf();
height = RHS.height;
rootSize = RHS.rootSize;
allocator = RHS.allocator;
// rootLeaf and rootBranch are both uninitialized. Move RHS data into
// appropriate field.
if (RHS.branched()) {
rootBranch() = std::move(RHS.rootBranch());
// Prevent RHS deallocating memory LHS now owns by replacing RHS
// rootBranch with a new rootLeaf.
RHS.rootBranch().~RootBranch();
RHS.height = 0;
new (&RHS.rootLeaf()) RootLeaf();
} else {
rootLeaf() = std::move(RHS.rootLeaf());
}
return *this;
}
///@}
~IntervalMap() { ~IntervalMap() {
clear(); clear();

View File

@ -18,15 +18,6 @@ typedef IntervalMap<unsigned, unsigned, 4> UUMap;
typedef IntervalMap<unsigned, unsigned, 4, typedef IntervalMap<unsigned, unsigned, 4,
IntervalMapHalfOpenInfo<unsigned>> UUHalfOpenMap; IntervalMapHalfOpenInfo<unsigned>> UUHalfOpenMap;
static_assert(!std::is_copy_constructible<UUMap>::value,
"IntervalMap copy constructor should be deleted");
static_assert(!std::is_move_constructible<UUMap>::value,
"IntervalMap move constructor should be deleted");
static_assert(!std::is_copy_assignable<UUMap>::value,
"IntervalMap copy assignment should be deleted");
static_assert(!std::is_move_assignable<UUMap>::value,
"IntervalMap move assignment should be deleted");
// Empty map tests // Empty map tests
TEST(IntervalMapTest, EmptyMap) { TEST(IntervalMapTest, EmptyMap) {
UUMap::Allocator allocator; UUMap::Allocator allocator;
@ -630,26 +621,73 @@ TEST(IntervalMapTest, RandomCoalescing) {
} }
static void setupOverlaps(UUMap &M) {
M.insert(10, 20, 0);
M.insert(30, 40, 0);
M.insert(50, 60, 0);
// Add extra nodes to force allocations.
for (int i = 70; i < 100; i += 2)
M.insert(i, i + 1, i);
}
static void checkOverlaps(UUMap &M) {
EXPECT_FALSE(M.overlaps(0, 9));
EXPECT_TRUE(M.overlaps(0, 10));
EXPECT_TRUE(M.overlaps(0, 15));
EXPECT_TRUE(M.overlaps(0, 25));
EXPECT_TRUE(M.overlaps(0, 45));
EXPECT_TRUE(M.overlaps(10, 45));
EXPECT_TRUE(M.overlaps(30, 45));
EXPECT_TRUE(M.overlaps(35, 36));
EXPECT_TRUE(M.overlaps(40, 45));
EXPECT_FALSE(M.overlaps(45, 45));
EXPECT_TRUE(M.overlaps(60, 60));
EXPECT_TRUE(M.overlaps(60, 66));
EXPECT_FALSE(M.overlaps(66, 66));
}
TEST(IntervalMapTest, Copy) {
// Test that copy assignment and initialization works.
UUHalfOpenMap::Allocator Allocator;
UUMap Src(Allocator);
setupOverlaps(Src);
UUMap CopyAssignmentDst(Allocator);
CopyAssignmentDst = Src;
UUMap CopyInitDst(Src);
checkOverlaps(Src);
Src.clear();
checkOverlaps(CopyAssignmentDst);
checkOverlaps(CopyInitDst);
}
TEST(IntervalMapTest, Move) {
// Test that move assignment and initialization works.
UUHalfOpenMap::Allocator Allocator;
// Double check that MoveAssignmentDst owns all its data by moving from an
// object that is destroyed before we call checkOverlaps.
UUMap MoveAssignmentDst(Allocator);
{
UUMap Src(Allocator);
setupOverlaps(Src);
MoveAssignmentDst = std::move(Src);
}
checkOverlaps(MoveAssignmentDst);
UUMap MoveInitSrc(Allocator);
setupOverlaps(MoveInitSrc);
UUMap MoveInitDst(std::move(MoveInitSrc));
checkOverlaps(MoveInitDst);
}
TEST(IntervalMapTest, Overlaps) { TEST(IntervalMapTest, Overlaps) {
UUMap::Allocator allocator; UUMap::Allocator allocator;
UUMap map(allocator); UUMap map(allocator);
map.insert(10, 20, 0); setupOverlaps(map);
map.insert(30, 40, 0); checkOverlaps(map);
map.insert(50, 60, 0);
EXPECT_FALSE(map.overlaps(0, 9));
EXPECT_TRUE(map.overlaps(0, 10));
EXPECT_TRUE(map.overlaps(0, 15));
EXPECT_TRUE(map.overlaps(0, 25));
EXPECT_TRUE(map.overlaps(0, 45));
EXPECT_TRUE(map.overlaps(10, 45));
EXPECT_TRUE(map.overlaps(30, 45));
EXPECT_TRUE(map.overlaps(35, 36));
EXPECT_TRUE(map.overlaps(40, 45));
EXPECT_FALSE(map.overlaps(45, 45));
EXPECT_TRUE(map.overlaps(60, 60));
EXPECT_TRUE(map.overlaps(60, 66));
EXPECT_FALSE(map.overlaps(66, 66));
} }
TEST(IntervalMapTest, OverlapsHalfOpen) { TEST(IntervalMapTest, OverlapsHalfOpen) {