My original deque implementation and efficiency question lacked move semantics. After ratchet freak provided an example of how to define them I decided to try adding them to my deque. The unit test + output below shows that the code is working.
However, my new question is: Are there edge cases in any of my move semantic functions that I may have forgotten, and is the data.~type();
necessary in the eNode<type>::operator=(type && other)
? I'd also appreciate any feedback on my coding style.
template<class type>
class eDeque;
//*************************************************
// eNode
// to be used with friend class eDeque<type>
//*************************************************
template<class type>
class eNode {
friend class eDeque<type>;
public :
// ... remainder of class definition (see referenced question)
explicit eNode(type && data) : prev(nullptr), next(nullptr) { std::swap(this->data, data); }; // move constructor
// DEBUG: destructor omitted because no heap allocation occurs
eNode<type> & operator=(eNode<type> && other) { // move assignment
data.~type();
std::swap(this->data, other.data);
return *this;};
// ... remainder of class definition (see referenced question)
private:
eNode<type> * prev;
eNode<type> * next;
type data;
};
template <class type>
class eDeque {
public:
// ... remainder of class definition (see referenced question)
eDeque(eDeque<type> && other); // move constructor
eDeque<type> & operator=(eDeque<type> && other); // move assignment
void PushFront(type && data); // emplace and move
void PushBack(type && data); // emplace and move
private:
eNode<type> * front;
eNode<type> * back;
int nodeCount;
// ... remainder of class definition (see referenced question)
};
//******************
// eDeque::eDeque
// move constructor
//******************
template <class type>
inline eDeque<type>::eDeque(eDeque<type> && other) : nodeCount(0), front(nullptr), back(nullptr) {
std::swap(nodeCount, other.nodeCount);
std::swap(front, other.front);
std::swap(back, other.back);
}
//******************
// eDeque::operator=
// move assignment
//******************
template <class type>
inline eDeque<type> & eDeque<type>::operator=(eDeque<type> && other) {
Clear();
std::swap(nodeCount, other.nodeCount);
std::swap(front, other.front);
std::swap(back, other.back);
return *this;
}
//******************
// eDeque::PushFront
// emplace and move
//******************
template <class type>
inline void eDeque<type>::PushFront(type && data) {
eNode<type> * newFront;
newFront = new eNode<type>(std::move(data));
if (front == nullptr) {
back = newFront;
front = newFront;
} else {
newFront->prev = front;
front->next = newFront;
front = newFront;
}
nodeCount++;
}
//******************
// eDeque::PushBack
// emplace and move
//******************
template <class type>
inline void eDeque<type>::PushBack(type && data) {
eNode<type> * newBack;
newBack = new eNode<type>(std::move(data));
if (back == nullptr) {
back = newBack;
front = newBack;
}
else {
newBack->next = back;
back->prev = newBack;
back = newBack;
}
nodeCount++;
}
The following is the parts of the unit test added to the original source plus some context:
#include "Deque.h"
// iterate and print back to front
template<class type>
void PrintDeque(const eDeque<type> & de, const char * name) {
eNode<int> * iterator;
int i;
printf("%s size == %i\n", name, de.Size());
for (i = 0, iterator = de.Back(); iterator != nullptr; iterator = iterator->Next())
printf("node %i == %i\n", i++, iterator->Data());
printf("\n\n");
}
// iterate and increment by amount back to front
template<class type>
void IncrementData(const eDeque<type> & de, int amount) {
eNode<int> * iterator;
for (iterator = de.Back(); iterator != nullptr; iterator = iterator->Next())
iterator->Data() += amount;
}
// unit test
int main() {
int lValues[] = { 0, 1, 2, 3, 4, 5, 6 };
printf("BEGIN eDeque COPY TESTS\n\n");
eDeque<int> original;
original.PushFront(lValues[0]);
original.PushFront(lValues[1]);
original.PushFront(lValues[2]);
original.PushFront(lValues[3]);
original.PushFront(lValues[4]);
original.PushFront(lValues[5]);
original.PushBack(lValues[6]);
original.PopBack();
original.PopFront();
PrintDeque(original, "original");
eDeque<int> B;
printf("B.Size() < original.Size()\n");
PrintDeque(B, "B_before_copyAssign");
B = original; // copy assignment test with B.Size() < original.Size()
printf("B = original\n");
PrintDeque(B, "B result");
eDeque<int> C;
// these are now emplace and move calls
C.PushBack(10);
C.PushFront(11);
C.PushBack(12);
C.PushFront(13);
C.PushBack(14);
C.PushFront(15);
C.PushBack(16);
C.PushFront(17);
printf("C.Size() > original.Size()\n");
PrintDeque(C, "C_before_copyAssign");
C = original; // copy assignment test with C.Size() > original.Size()
printf("C = original\n");
PrintDeque(C, "C result");
// verify the original is intact
printf("original deque intact\n");
PrintDeque(original, "original");
IncrementData(C, 5);
printf("incremented each node of C by 5\n");
PrintDeque(C, "C result");
printf("B.Size() == C.Size()\n");
B = C; // copy assignment test with B.Size() == C.Size()
printf("B = C\n");
PrintDeque(B, "B result");
// BEGIN eDeque move semantics testing
eDeque<int> G = eDeque<int>(B); // copy construct an rvalue eDeque, then move construct G
printf("eDeque<int> G = eDeque<int>(B) move construction\n");
PrintDeque(G, "G result");
IncrementData(B, 10);
printf("incremented each node of B by 10\n");
PrintDeque(B, "B result");
G = eDeque<int>(std::move(B)); // move construct an rvalue eDeque, then move assign G (B becomes undefined/unspecified)
printf("G = eDeque<int>(B) move assignment (B becomes unspecified)\n");
PrintDeque(G, "G result");
PrintDeque(B, "B result");
int lValue = 666;
printf("G.PushBack(std::move(lValue == 666)); G.PushFront(777) emplace and move\n");
G.PushBack(std::move(lValue)); // emplace and move
G.PushFront(777); // emplace and move
PrintDeque(G, "G result");
// END eDeque move semantics testing
printf("END eDeque COPY TESTS\n\n");
printf("BEGIN eNODE COPY TESTS\n\n");
// BEGIN eNode move semantics testing
eNode<int> H = eNode<int>(123); // move construction
printf("eNode<int> H = eNode<int>(123) move construction result: H.Data() == %i\n\n", H.Data());
H = eNode<int>(456); // move assignment
printf("H = eNode<int>(456) move assignment result: H.Data() == %i, H.Next() == %p, H.Prev() == %p\n\n", H.Data(), H.Next(), H.Prev());
printf("G.PushFront(std::move(H.Data()))\n");
G.PushFront(std::move(H.Data())); // H.data now unspecified
PrintDeque(G, "G result");
// END eNode move semantics testing
printf("END eNODE COPY TESTS\n\n");
return 0;
}
The following is the output of the relevant unit test code:
BEGIN eDeque COPY TESTS
original size == 5
node 0 == 0
node 1 == 1
node 2 == 2
node 3 == 3
node 4 == 4
B.Size() < original.Size()
B_before_copyAssign size == 0
B = original
B result size == 5
node 0 == 0
node 1 == 1
node 2 == 2
node 3 == 3
node 4 == 4
C.Size() > original.Size()
C_before_copyAssign size == 8
node 0 == 16
node 1 == 14
node 2 == 12
node 3 == 10
node 4 == 11
node 5 == 13
node 6 == 15
node 7 == 17
C = original
C result size == 5
node 0 == 0
node 1 == 1
node 2 == 2
node 3 == 3
node 4 == 4
original deque intact
original size == 5
node 0 == 0
node 1 == 1
node 2 == 2
node 3 == 3
node 4 == 4
incremented each node of C by 5
C result size == 5
node 0 == 5
node 1 == 6
node 2 == 7
node 3 == 8
node 4 == 9
B.Size() == C.Size()
B = C
B result size == 5
node 0 == 5
node 1 == 6
node 2 == 7
node 3 == 8
node 4 == 9
eDeque<int> G = eDeque<int>(B) move construction
G result size == 5
node 0 == 5
node 1 == 6
node 2 == 7
node 3 == 8
node 4 == 9
incremented each node of B by 10
B result size == 5
node 0 == 15
node 1 == 16
node 2 == 17
node 3 == 18
node 4 == 19
G = eDeque<int>(B) move assignment (B becomes unspecified)
G result size == 5
node 0 == 15
node 1 == 16
node 2 == 17
node 3 == 18
node 4 == 19
B result size == 0
G.PushBack(std::move(lValue == 666)); G.PushFront(777) emplace and move
G result size == 7
node 0 == 666
node 1 == 15
node 2 == 16
node 3 == 17
node 4 == 18
node 5 == 19
node 6 == 777
END eDeque COPY TESTS
BEGIN eNODE COPY TESTS
eNode<int> H = eNode<int>(123) move construction result: H.Data() == 123
H = eNode<int>(456) move assignment result: H.Data() == 456, H.Next() == 0000000
0, H.Prev() == 00000000
G.PushFront(std::move(H.Data()))
G result size == 8
node 0 == 666
node 1 == 15
node 2 == 16
node 3 == 17
node 4 == 18
node 5 == 19
node 6 == 777
node 7 == 456
END eNODE COPY TESTS
eDeque<std::string>
and then witheDeque<std::unique_ptr<int>>
. \$\endgroup\$std::string
worked fine, butstd::unique_ptr<type>
usage exposed my lack of aeNode ( eNode<type> && other)
move constructor. Fixed. \$\endgroup\$eNode<std::string> H("you might need more than 24 characters here"); H = eNode<std::string>("123");
should not have worked, because of that explicit destructor call you were worried about. (You were right to be worried.) Where do you even use that assignment operator, though? I don't think your deque relies on that functionality (nor should it). \$\endgroup\$