I want to improve my algorithms and data structures in C++ understanding and also learn how the standard library containers are implemented. I implemented a std::vector in a previous review and this time have turned to the std::list.
My questions are:
Are there any memory allocation/deallocation errors in this implementation?
Is the design ok? Can it be improved?
Any bugs?
Any feedback not mentioned above would be interesting.
The linked list implementation is in list.hpp
There is a test.cpp which is a set of tests of the functionality using gtest. To build this you need to install gtest and: make testAll ./testAll
list.hpp:
#ifndef LIST_HPP_
#define LIST_HPP_
#include <cstddef>
#include <initializer_list>
#include <utility>
template< typename T >
class list {
public:
struct node {
node(T const& v) : value(v), next(nullptr), prior(nullptr) { }
// learn about these variadic arguments
template <typename ...Args>
node(Args &&...v) : value(std::forward<Args>(v)...), next(nullptr), prior(nullptr) { }
T value;
node* next;
node* prior;
};
struct iterator {
iterator(node* nod) : ptr_(nod) {}
iterator& operator++() {
if (ptr_) {
ptr_ = ptr_->next;
}
return *this;
}
iterator operator++(int) {
auto old = *this;
if (ptr_) {
ptr_ = ptr_->next;
}
return old;
}
T& operator*() const { return ptr_->value; }
T* operator->() { return &ptr_->value; }
bool operator==(const iterator& other) { return ptr_ == other.ptr_; }
bool operator!=(const iterator& other) { return ptr_ != other.ptr_; }
node* ptr_;
};
// O(1)
list() : head_(nullptr), tail_(nullptr), size_(0) {}
// O(n)
template< typename input_iterator >
list(input_iterator first, input_iterator last) : head_(nullptr), tail_(nullptr), size_(0) {
for (auto it = first; it != last; ++it) {
push_back(*it);
}
}
// O(n)
list(std::initializer_list<T> init) : list<T>(init.begin(), init.end()) {}
// O(n) - copy constructor
list(const list& other) : head_(nullptr), tail_(nullptr), size_(0) {
auto it = other.begin();
while (it != nullptr) {
push_back(*it);
++it;
}
size_ = other.size();
}
// O(n) copy assignment
// - because although we just swap pointers, note list is copied - first arg is copied
// note also that we don't need to define a copy assignment move operator - this one does it!
// If called with an rvalue reference will use move constructor to create other and then swap with the current state.
list& operator=(list other)
{
swap(*this, other);
return *this;
}
// O(1) move constructor
list(list&& other) noexcept : list() {
swap(*this, other);
}
// O(n)
~list() {
clear();
}
// O(1) - just 3 swaps
friend void swap(list& first, list& second) // nothrow
{
// by swapping the members of list,
// first and second are effectively swapped
std::swap(first.size_, second.size_);
std::swap(first.head_, second.head_);
std::swap(first.tail_, second.tail_);
}
// O(n)
void clear() {
if (head_) {
node* current = head_;
while (current) {
node* next = current->next;
delete current;
current = next;
}
}
head_ = nullptr;
tail_ = nullptr;
size_ = 0;
}
// O(1)
bool empty() const {
return head_ == nullptr;
}
// O(n)
bool operator==(const list<T>& other) {
if (size_ != other.size()) {
return false;
}
for (auto it = begin(), it2 = other.begin(); it != end(); ++it, ++it2) {
if (*it != *it2) {
return false;
}
}
return true;
}
// O(1)
// pos - iterator before which the content will be inserted. pos may be the end() iterator
// returns iterator pointing to the inserted value
iterator insert(iterator pos, const T& value) {
node* inserted = new node;
inserted->value = value;
inserted->next = pos.ptr_;
// if pos.ptr_ is null means inserting at end of list
if (pos.ptr_ == nullptr) {
inserted->prior = tail_;
if (tail_ != nullptr) {
tail_->next = inserted;
}
else {
// if tail_ is null, head_ must also be null
head_ = tail_ = inserted;
}
tail_ = inserted;
}
else {
inserted->prior = pos.ptr_->prior;
pos.ptr_->prior = inserted;
}
// if inserted is now at head_, update head_
if (inserted->prior == nullptr) {
head_ = inserted;
}
return inserted;
}
// O(1)
void push_back(const T& value) {
node* newnode = make_node(value);
if (tail_) {
node* oldtail = tail_;
oldtail->next = newnode;
newnode->prior = oldtail;
tail_ = newnode;
}
else {
head_ = tail_ = newnode;
}
++size_;
}
// O(1)
void push_front(const T& value) {
node* newnode = make_node(value);
if (head_) {
newnode->next = head_;
newnode->value = value;
head_->prior = newnode;
head_ = newnode;
}
else {
head_ = tail_ = newnode;
}
++size_;
}
// O(1)
size_t size() const {
return size_;
}
// O(1)
iterator begin() {
return iterator(head_);
}
const iterator begin() const {
return iterator(head_);
}
// O(1)
iterator end() {
return nullptr;
}
const iterator end() const {
return nullptr;
}
// O(1)
T& front() { return *iterator(head_); }
const T& front() const { return *iterator(head_); }
// O(1)
T& back() { return *iterator(tail_); }
const T& back() const { return *iterator(tail_); }
// O(1)
void pop_back() {
if (tail_) {
node* newtail = tail_->prior;
if (newtail) {
newtail->next = nullptr;
}
else {
// means that head_ has also been erased
head_ = nullptr;
}
delete tail_;
tail_ = newtail;
--size_;
}
}
// O(1)
void pop_front() {
if (head_) {
node* newhead = head_->next;
if (newhead) {
newhead->prior = nullptr;
}
else {
// means that head_ has also been erased
head_ = nullptr;
}
delete head_;
head_ = newhead;
--size_;
}
}
// O(n)
size_t remove(const T& value) {
size_t count{ 0 };
node* current = head_;
while (current) {
node* next = current->next;
// if value exists, remove item
if (current->value == value) {
/*iterator next_it =*/ erase(current);
//next = next_it.ptr_;
++count;
}
current = next;
}
return count;
}
// O(1)
// pos must be dereferenceable - ie cannot pass in end
// return iterator following the last removed element
iterator erase(iterator pos) {
node* before = pos.ptr_->prior;
node* after = pos.ptr_->next;
// remove pos
if (before) {
before->next = after;
}
else {
head_ = after;
}
if (after) {
after->prior = before;
}
else {
// if there is no element after, means that head_ must be set to before
tail_ = before;
}
--size_;
delete pos.ptr_;
pos.ptr_ = nullptr;
return iterator(after);
}
// O(1)
// pos - element before which the content will be inserted
void splice(iterator pos, list& other) {
// this size becomes += other.size()
this->size_ += other.size();
// other size becomes zero
other.size_ = 0;
// pos->prior->next point to other.begin()
node* priornode = pos.ptr_->prior;
if (priornode) {
iterator beg(other.begin());
priornode->next = beg.ptr_;
}
else {
// reset head to be start of other list
head_ = other.head_;
}
// pos->prior point to last node in other
pos.ptr_->prior = other.tail_;
// last in other point to pos
other.tail_->next = pos.ptr_;
// other head_ point to node prior to pos
other.head_->prior = priornode;
other.head_ = other.tail_ = nullptr;
}
// O(n)
void reverse() {
node* current = head_;
while (current) {
node* next = current->next;
// swap prior & next
node* tmp = current->next;
current->next = current->prior;
current->prior = tmp;
current = next;
}
// finally we swap head_ & tail_
node* oldhead = head_;
head_ = tail_;
tail_ = oldhead;
}
// O(1)
template<typename... P>
void emplace_back(P&&... v)
{
node* newnode = new node(std::forward<P>(v)...);
newnode->next = nullptr;
newnode->prior = nullptr;
if (tail_) {
node* oldtail = tail_;
oldtail->next = newnode;
newnode->prior = oldtail;
tail_ = newnode;
}
else {
head_ = tail_ = newnode;
}
++size_;
}
private:
node* make_node(const T& value) {
return new node(value);
}
node* head_;
node* tail_;
size_t size_;
};
#endif // LIST_HPP_
test.cpp:
#include "list.hpp"
#include <numeric>
//// debugging
#include <iostream>
#include "gtest/gtest.h"
// *** TODO - need to create a copy ctor so this will not fail
template<typename T>
list<T> make_list() {
list<T> mylist;
int numbers[]{ 0, 1, 2, 3, 4 };
for (auto& n : numbers) {
mylist.push_back(n);
}
return mylist;
}
// push_back increases size by 1 each time
TEST(push_back, push_back_increases_size_by_one) {
list<int> mylist;
EXPECT_EQ(mylist.size(), 0);
int numbers[]{0, 1, 2, 3, 4};
size_t size = sizeof(numbers) / sizeof(numbers[0]);
size_t entries = 0;
for (auto& n : numbers) {
mylist.push_back(n);
++entries;
EXPECT_EQ(mylist.size(), entries);
}
}
// push_front increases size by 1 each time
TEST(push_front, push_front_increases_size_by_one) {
list<int> mylist;
EXPECT_EQ(mylist.size(), 0);
int numbers[]{ 0, 1, 2, 3, 4 };
size_t size = sizeof(numbers) / sizeof(numbers[0]);
size_t entries = 0;
for (auto& n : numbers) {
mylist.push_front(n);
++entries;
EXPECT_EQ(mylist.size(), entries);
}
}
// iterate elements
TEST(iterate, add_3_elements_able_to_iterate_each) {
list<int> mylist;
EXPECT_EQ(mylist.size(), 0);
int numbers[]{ 1, 2, 3};
size_t size = sizeof(numbers) / sizeof(numbers[0]);
size_t entries = 0;
for (auto& n : numbers) {
mylist.push_front(n);
++entries;
EXPECT_EQ(mylist.size(), entries);
}
// elements sum to 6
int result = std::accumulate(mylist.begin(), mylist.end(), 0);
ASSERT_EQ(result, 6);
std::cout << "values in linked list sum to: " << result << std::endl;
auto it = mylist.begin();
std::cout << "1st value: " << *it << std::endl;
ASSERT_EQ(*it, numbers[2]);
it++;
std::cout << "2nd value: " << *it << std::endl;
ASSERT_EQ(*it, numbers[1]);
++it;
std::cout << "3rd value: " << *it << std::endl;
ASSERT_EQ(*it, numbers[0]);
}
TEST(front, add_elements_always_get_front_element) {
list<int> mylist;
mylist.push_back(2);
EXPECT_EQ(mylist.front(), 2);
mylist.push_front(1);
EXPECT_EQ(mylist.front(), 1);
mylist.push_back(3);
// should be now in sequence from front to back, 1,2,3
EXPECT_EQ(mylist.front(), 1);
}
TEST(back, add_elements_always_get_back_element) {
list<int> mylist;
mylist.push_back(2);
EXPECT_EQ(mylist.back(), 2);
mylist.push_front(1);
EXPECT_EQ(mylist.back(), 2);
mylist.push_back(3);
// should be now in sequence from front to back, 1,2,3
EXPECT_EQ(mylist.back(), 3);
}
TEST(pop_back, elements_popped_in_correct_sequence) {
list<int> mylist;
EXPECT_EQ(mylist.size(), 0);
int numbers[]{ 1, 2, 3 };
size_t size = sizeof(numbers) / sizeof(numbers[0]);
size_t entries = 0;
for (auto& n : numbers) {
mylist.push_back(n);
++entries;
EXPECT_EQ(mylist.size(), entries);
}
while (!mylist.empty()) {
std::cout << "back item: " << mylist.back() << std::endl;
EXPECT_EQ(mylist.back(), numbers[entries-1]);
mylist.pop_back();
entries--;
EXPECT_EQ(mylist.size(), entries);
}
}
TEST(pop_front, elements_popped_in_correct_sequence) {
list<int> mylist;
EXPECT_EQ(mylist.size(), 0);
int numbers[]{ 1, 2, 3 };
size_t size = sizeof(numbers) / sizeof(numbers[0]);
size_t entries = 0;
for (auto& n : numbers) {
mylist.push_back(n);
++entries;
EXPECT_EQ(mylist.size(), entries);
}
while (!mylist.empty()) {
std::cout << "front item: " << mylist.front() << std::endl;
EXPECT_EQ(mylist.front(), numbers[size - entries]);
mylist.pop_front();
entries--;
EXPECT_EQ(mylist.size(), entries);
}
}
TEST(clear, clear_causes_size_zero) {
list<int> mylist;
EXPECT_EQ(mylist.size(), 0);
int numbers[]{ 0, 1, 2, 3, 4 };
size_t size = sizeof(numbers) / sizeof(numbers[0]);
size_t entries = 0;
for (auto& n : numbers) {
mylist.push_front(n);
++entries;
EXPECT_EQ(mylist.size(), entries);
}
EXPECT_NE(mylist.size(), 0);
mylist.clear();
EXPECT_EQ(mylist.size(), 0);
}
TEST(clear, clear_and_start_again_succeeds) {
list<int> mylist;
EXPECT_EQ(mylist.size(), 0);
int numbers[]{ 0, 1, 2, 3, 4 };
size_t entries = 0;
for (auto& n : numbers) {
mylist.push_front(n);
++entries;
EXPECT_EQ(mylist.size(), entries);
}
EXPECT_NE(mylist.size(), 0);
mylist.clear();
entries = 0;
EXPECT_EQ(mylist.size(), 0);
for (auto& n : numbers) {
mylist.push_front(n);
++entries;
EXPECT_EQ(mylist.size(), entries);
}
}
TEST(copy_constructor, copy_constructor_deep_copy_values) {
list<int> mylist = make_list<int>();
EXPECT_EQ(mylist.size(), 5);
list<int> list2(mylist);
EXPECT_EQ(list2.size(), 5);
EXPECT_EQ(list2.front(), mylist.front());
EXPECT_EQ(list2.back(), mylist.back());
}
TEST(assignment, assignment_constructor_deep_copy_values) {
list<int> mylist = make_list<int>();
EXPECT_EQ(mylist.size(), 5);
list<int> list2;
list2.push_back(3);
list2 = mylist;
EXPECT_EQ(list2.size(), 5);
EXPECT_EQ(list2.front(), mylist.front());
EXPECT_EQ(list2.back(), mylist.back());
}
TEST(assignment, elements_added_correctly) {
// create a new resizing array from an initialisation_list
list<int> lst1{ 1,2,3,4,5 };
// assignment operator test
// will crash if haven't implemented operator=
list<int> list2 = lst1;
EXPECT_EQ(lst1.size(), list2.size());
for (auto it1 = lst1.begin(), it2 = list2.begin(); it1 != lst1.end(); ++it1, ++it2) {
EXPECT_EQ(*it1, *it2);
}
}
// to test move constructor
list<int> fill(const std::vector<int>& input, int*& ptr) {
list<int> list1;
for (auto it = input.begin(); it != input.end(); ++it) {
list1.push_back(*it);
}
ptr = &(*list1.begin());
return list1;
}
TEST(move_constructor, elements_moved) {
// move constructor and move assignment test
std::vector<int> input{ 1,2,3,4,5,6,7,8,9,10 };
list<int>::iterator hdptr = nullptr;
int* ptr = nullptr;
list<int> l1 = fill(input, ptr);
EXPECT_EQ(l1.size(), input.size());
// head ptr of l1 returned must be same as head ptr of list1 in fill function - if move ctor correct
int* ptrl1 = &(*l1.begin());
EXPECT_EQ(ptr, ptrl1);
// old list must be empty now - not sure how to check that - in fill function - maybe have to make list1 in there global?
auto it2 = input.begin();
for (auto it1 = l1.begin(); it1 != l1.end(); ++it1, ++it2) {
EXPECT_EQ(*it1, *it2);
}
}
TEST(insert, insert_at_beginning_position_check) {
list<int> mylist;
mylist.push_back(1);
mylist.push_back(2);
list<int>::iterator it = mylist.begin();
// ++it;
list<int>::iterator inserted = mylist.insert(it, 99);
// insert returns iterator to inserted item
EXPECT_EQ(*inserted, 99);
// front element should be value 99
EXPECT_EQ(mylist.front(), 99);
// last element should remain as value 2
EXPECT_EQ(mylist.back(), 2);
}
TEST(insert, insert_middle_position_check) {
list<int> mylist;
mylist.push_back(1);
mylist.push_back(2);
list<int>::iterator it = mylist.begin();
++it; // now points to last element
list<int>::iterator inserted = mylist.insert(it, 99);
// insert returns iterator to inserted item
EXPECT_EQ(*inserted, 99);
// front element should be value 1
EXPECT_EQ(mylist.front(), 1);
// last element should be value 2
EXPECT_EQ(mylist.back(), 2);
}
TEST(insert, insert_end_position_check) {
list<int> mylist;
mylist.push_back(1);
mylist.push_back(2);
list<int>::iterator it = mylist.end();
list<int>::iterator inserted = mylist.insert(it, 99);
// insert returns iterator to inserted item
EXPECT_EQ(*inserted, 99);
// front element should be value 1
EXPECT_EQ(mylist.front(), 1);
// last element should be value 2
EXPECT_EQ(mylist.back(), 99);
}
TEST(insert, insert_no_existing_nodes) {
list<int> mylist;
list<int>::iterator it = mylist.begin();
list<int>::iterator inserted = mylist.insert(it, 99);
// insert returns iterator to inserted item
EXPECT_EQ(*inserted, 99);
// front element should be value 1
EXPECT_EQ(mylist.front(), 99);
// last element should be value 2
EXPECT_EQ(mylist.back(), 99);
}
TEST(erase, erase_at_beginning_position_check) {
list<int> mylist;
mylist.push_back(1);
mylist.push_back(2);
list<int>::iterator it = mylist.begin();
list<int>::iterator after_erased = mylist.erase(it);
// erase returns iterator to element after element erased
EXPECT_EQ(*after_erased, 2);
// front element should be value 2
EXPECT_EQ(mylist.front(), 2);
// last element should remain as value 2
EXPECT_EQ(mylist.back(), 2);
// should be 1 element remaining
EXPECT_EQ(mylist.size(), 1);
}
TEST(erase, erase_end_position) {
list<int> mylist;
mylist.push_back(1);
mylist.push_back(2);
list<int>::iterator it = mylist.begin();
++it;
// erase 2 - after erased will be end
list<int>::iterator after_erased = mylist.erase(it);
// front element should be value 1
EXPECT_EQ(mylist.front(), 1);
// last element should remain as value 1
EXPECT_EQ(mylist.back(), 1);
// should be 1 element remaining
EXPECT_EQ(mylist.size(), 1);
}
TEST(erase, erase_middle_position) {
list<int> mylist;
mylist.push_back(1);
mylist.push_back(2);
mylist.push_back(3);
list<int>::iterator it = mylist.begin();
++it;
// erase 2 - after erased will be 3
list<int>::iterator after_erased = mylist.erase(it);
// erase returns iterator to element after element erased
EXPECT_EQ(*after_erased, 3);
// front element should be value 1
EXPECT_EQ(mylist.front(), 1);
// last element should remain as value 1
EXPECT_EQ(mylist.back(), 3);
// should be 1 element remaining
EXPECT_EQ(mylist.size(), 2);
}
TEST(reverse, start_mid_end_reversed) {
list<int> mylist;
mylist.push_back(1);
mylist.push_back(2);
mylist.push_back(3);
mylist.reverse();
list<int>::iterator it = mylist.begin();
EXPECT_EQ(*it, 3);
++it;
// middle element still 2
EXPECT_EQ(*it, 2);
++it;
// last element 1
EXPECT_EQ(*it, 1);
// should still be 3 elements in list
EXPECT_EQ(mylist.size(), 3);
}
TEST(reverse, start_end_reversed) {
list<int> mylist;
mylist.push_back(1);
mylist.push_back(2);
mylist.reverse();
// front element should be value 1
EXPECT_EQ(mylist.front(), 2);
// last element should remain as value 1
EXPECT_EQ(mylist.back(), 1);
}
TEST(reverse, single_item_no_change) {
list<int> mylist;
mylist.push_back(1);
mylist.reverse();
// front element should be value 1
EXPECT_EQ(mylist.front(), 1);
// last element should remain as value 1
EXPECT_EQ(mylist.back(), 1);
}
TEST(reverse, no_elements_no_change) {
list<int> mylist;
mylist.reverse();
EXPECT_EQ(mylist.size(), 0);
}
TEST(splice, splice_beginning_first_list) {
list<int> list1;
list1.push_front(2);
list1.push_front(1);
list<int> list2;
list2.push_front(4);
list2.push_front(3);
auto it = list1.begin();
list1.splice(it, list2);
EXPECT_EQ(list1.size(), 4);
EXPECT_EQ(list2.size(), 0);
it = list1.begin();
EXPECT_EQ(*it, 3);
it++;
EXPECT_EQ(*it, 4);
it++;
EXPECT_EQ(*it, 1);
it++;
EXPECT_EQ(*it, 2);
}
TEST(splice, splice_middle_first_list) {
list<int> list1;
list1.push_front(2);
list1.push_front(1);
list<int> list2;
list2.push_front(4);
list2.push_front(3);
auto it = list1.begin();
it++;
list1.splice(it, list2);
EXPECT_EQ(list1.size(), 4);
EXPECT_EQ(list2.size(), 0);
it = list1.begin();
EXPECT_EQ(*it, 1);
it++;
EXPECT_EQ(*it, 3);
it++;
EXPECT_EQ(*it, 4);
it++;
EXPECT_EQ(*it, 2);
}
TEST(remove, elements_removed_correctly) {
list<int> mylist;
EXPECT_EQ(mylist.size(), 0);
int numbers[]{ 1,2,3,1,2,3 };
size_t size = sizeof(numbers) / sizeof(numbers[0]);
size_t entries = 0;
for (auto& n : numbers) {
mylist.push_back(n);
++entries;
EXPECT_EQ(mylist.size(), entries);
}
size_t removed = mylist.remove(2);
EXPECT_EQ(removed, 2);
EXPECT_EQ(mylist.size(), 4);
for (auto it = mylist.begin(); it != mylist.end(); ++it) {
EXPECT_NE(*it, 2);
}
}
TEST(iterator_constructor, elements_added_correctly) {
std::vector<int> vec{1,2,3,4,5,6,7,8,9,10};
list<int> mylist{vec.begin(), vec.end()};
EXPECT_EQ(mylist.size(), 10);
int index = 0;
for (auto it = mylist.begin(); it != mylist.end(); ++it) {
EXPECT_EQ(*it, vec[index++]);
}
}
TEST(initialiser_list_constructor, elements_added_correctly) {
list<int> mylist{ 1,2,3,4,5,6,7,8,9,10 };
EXPECT_EQ(mylist.size(), 10);
int number = 1;
for (auto it = mylist.begin(); it != mylist.end(); ++it) {
EXPECT_EQ(*it, number++);
}
}
TEST(operatorequals, list_comparison) {
list<int> mylist{ 1,2,3,4,5,6,7,8,9,10 };
EXPECT_EQ(mylist.size(), 10);
list<int> mylist2(mylist);
EXPECT_TRUE(mylist == mylist2);
}
struct President
{
std::string name;
std::string country;
int year;
//President() : year(0) {}
President(std::string p_name, std::string p_country, int p_year)
: name(std::move(p_name)), country(std::move(p_country)), year(p_year)
{
std::cout << "I am being constructed.\n";
}
President(President&& other)
: name(std::move(other.name)), country(std::move(other.country)), year(other.year)
{
std::cout << "I am being moved.\n";
}
President& operator=(const President& other) = default;
};
TEST(emplace_back, push_back_emplace) {
list<President> elections;
elections.emplace_back("Nelson Mandela", "South Africa", 1994);
President& p = elections.front();
EXPECT_EQ(p.year, 1994);
EXPECT_EQ(p.name, "Nelson Mandela");
EXPECT_EQ(p.country, "South Africa");
}
class Person {
public:
Person(const std::string& name, const int age, const int height_cm)
: name_(name), age_(age), height_cm_(height_cm) {}
// just used for checking - maybe we don't need?
~Person() {
std::cout << name_ << " Person destructor called\n";
}
std::string how_tall() const {
if (height_cm_ < 154)
return "short";
else if (height_cm_ < 183)
return "medium";
else
return "tall";
}
std::string how_old() const {
if (age_ < 40)
return "young";
else if (age_ < 60)
return "don't ask";
else
return "ancient";
}
private:
std::string name_;
int age_;
int height_cm_;
};
TEST(push_back, push_back_non_trivial_object) {
list<Person> persons;
Person Robert("Robert De Niro", 78, 171);
Person Julia("Julia Roberts", 54, 175);
persons.push_back(Robert);
persons.push_back(Julia);
Person& pref = persons.front();
EXPECT_EQ(pref.how_tall(), "medium");
EXPECT_EQ(pref.how_old(), "ancient");
Person& prefb = persons.back();
EXPECT_EQ(prefb.how_tall(), "medium");
EXPECT_EQ(prefb.how_old(), "don't ask");
}
TEST(pop_back, pop_back_non_trivial_object) {
list<Person> persons;
Person Robert("Robert De Niro", 78, 171);
Person Julia("Julia Roberts", 54, 175);
persons.push_back(Robert);
persons.push_back(Julia);
EXPECT_EQ(persons.size(), 2);
persons.pop_back();
EXPECT_EQ(persons.size(), 1);
persons.pop_back();
EXPECT_EQ(persons.size(), 0);
}
If you need to build it then you need Main_TestAll.cpp:
#include <limits.h>
#include "gtest/gtest.h"
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
and the Makefile:
CXX = g++
CXXFLAGS = -g -L/usr/local/lib -std=c++11
LIBS = -lgtest_main -lgtest -lpthread
INCS = -I./ -I/usr/local/include
CPPSOURCES = test.cpp
OBJS = $(CPPSOURCES:.cpp=.o)
testAll: $(OBJS)
$(CXX) $(CXXFLAGS) $(INCS) -o testAll Main_TestAll.cpp $(OBJS) $(LIBS)
.cpp.o:
$(CXX) $(CXXFLAGS) -c $< -o $@ $(INCS)
clean:
rm testAll *.o testAll.xml
std::list
, which allocates objects via an Allocator (whose type is a template parameter). Eliminating the Allocator changes the code to allocate and create objects, and eliminates some of the edge cases withsplice
. \$\endgroup\$