use_count becomes -1 when using shared_ptr in C++ - c++

GenericStack.h
#ifndef _GENERIC_STACK_TROFIMOV_H_
#define _GENERIC_STACK_TROFIMOV_H_
#include <memory>
class GenericStack {
struct StackNode {
std::shared_ptr<void> _data;
StackNode* _next;
StackNode(const std::shared_ptr<void>& p, StackNode* next)
: _data(p), _next(next) {
}
};
StackNode* _top;
GenericStack(const GenericStack&);
GenericStack& operator=(const GenericStack&);
protected:
GenericStack();
~GenericStack();
void push(const std::shared_ptr<void>&);
void pop();
std::shared_ptr<void>& top();
bool isEmpty() const;
public:
class EmptyError {
const char* _message;
public:
EmptyError(const char* message)
: _message(message) {
}
const char* getMessage() const {
return _message;
}
};
};
template <class T>
class TStack: private GenericStack {
public:
void push(const std::shared_ptr<T>& p) { GenericStack::push(p); }
void pop() { GenericStack::pop(); }
std::shared_ptr<T>& top() { return std::static_pointer_cast<T>(GenericStack::top()); }
bool isEmpty() const { return GenericStack::isEmpty(); }
};
#endif
GenerickStack.cpp
#include "GenericStack.h"
GenericStack::GenericStack()
: _top(0) {
};
GenericStack::~GenericStack() {
while(!isEmpty()) {
pop();
}
};
void GenericStack::push(const std::shared_ptr<void>& p) {
_top = new StackNode(p, _top);
}
std::shared_ptr<void>& GenericStack::top() {
if(isEmpty()) {
throw EmptyError("No more elements in stack.");
}
return _top->_data;
}
void GenericStack::pop() {
if(isEmpty()) {
throw EmptyError("No more elements in stack.");
}
StackNode* t = _top->_next;
delete _top;
_top = t;
}
bool GenericStack::isEmpty() const {
return !_top;
}
Main.cpp
#include <iostream>
#include "GenericStack.h"
//#define NDEBUG
#include <assert.h>
void ordinaryUsageVerification() {
TStack<int> intStack;
{
std::shared_ptr<int> sh(new int(7));
intStack.push(sh);
intStack.isEmpty();
assert(!intStack.isEmpty() && sh.use_count() == 2);
}
//assert(!intStack.isEmpty() && intStack.top().use_count() == 1);
std::cout << "intStack.top().use_count(): " << intStack.top().use_count() << std::endl;
std::cout << "*gs.top(): " << *intStack.top() << std::endl;
intStack.pop();
assert(intStack.isEmpty());
}
int main() {
ordinaryUsageVerification();
return 0;
}
After the following two line in Main.cpp:
std::shared_ptr<int> sh(new int(7));
intStack.push(sh);
I am expecting intStack.top().use_count() to be equal 2, but it is equal -1.
I am expecting such a behavior because when calling a push method I am passing the shared_ptr by reference, so the use_count should not change. And only in one place in GenericaStack.h here:
StackNode(const std::shared_ptr<void>& p, StackNode* next)
: _data(p), _next(next) {
The use_count increments by one for p.
So, given that before push I had sh.use_count() == 1 and after intStack.push(sh); I had sh.use_count() == 2 I should get intStack.top().use_count() == 2, but what I am getting is intStack.top().use_count() == -1. Why?
Thank you.
After modifying GenericStack.h in this way:
#ifndef _GENERIC_STACK_TROFIMOV_H_
#define _GENERIC_STACK_TROFIMOV_H_
#include <memory>
class GenericStack {
struct StackNode {
std::shared_ptr<void> _data;
StackNode* _next;
StackNode(std::shared_ptr<void>& p, StackNode* next)
: _data(p), _next(next) {
}
};
StackNode* _top;
GenericStack(const GenericStack&);
GenericStack& operator=(const GenericStack&);
protected:
GenericStack();
~GenericStack();
void push(std::shared_ptr<void>&);
void pop();
std::shared_ptr<void>& top();
bool isEmpty() const;
public:
class EmptyError {
const char* _message;
public:
EmptyError(const char* message)
: _message(message) {
}
const char* getMessage() const {
return _message;
}
};
};
template <class T>
class TStack: private GenericStack {
public:
void push(std::shared_ptr<T>& p) {
GenericStack::push(p);
}
void pop() { GenericStack::pop(); }
std::shared_ptr<T> top() { return std::static_pointer_cast<T>(GenericStack::top()); }
bool isEmpty() const { return GenericStack::isEmpty(); }
};
#endif
I am getting an error:
Error 1 error C2664: 'GenericStack::push' : cannot convert parameter 1 from 'std::shared_ptr<_Ty>' to 'std::shared_ptr<_Ty> &' ...\stack\genericstack.h 47 Stack
It is about this part:
void push(std::shared_ptr<T>& p) {
GenericStack::push(p);
}

Let's review the specification of std::static_pointer_cast: std::static_pointer_cast() returns an rvalue, a.k.a. a temporary object.
std::shared_ptr<T>& top() { return std::static_pointer_cast<T>(GenericStack::top()); }
This returns a reference to a temporary object that gets destroyed by the time this top() returns. Undefined behavior. Most modern C++ compilers are generally capable of detecting this common instance of undefined behavior, and your compiler should be barking at you, on this line.
In general, attempts to defeat C++'s type-safety, by casting things back and forth from a void * (the apparent underlying purpose of this set of templates) -- that never ends well.

Related

Return cannot convert

I have this code below, and I am having a hard time getting it to work (operators in the movable_ptr<T> class, to be precise).
//movable_ptr.hpp
//Michal Cermak
#ifndef MOVABLE_H
#define MOVABLE_H
template<typename T> class movable_ptr;
template<typename T> class enable_movable_ptr {
public:
//default constructor
enable_movable_ptr() : ptr_(nullptr) {};
enable_movable_ptr(T* p) : ptr_(p) {};
//copy
enable_movable_ptr(const enable_movable_ptr<T>&) {};
enable_movable_ptr& operator=(const enable_movable_ptr<T>& p) {
T* new_ptr = p.first_;
movable_ptr<T>* new_first = p.first_;
delete p;
ptr_ = new_ptr;
first_ = new_first;
return this;
};
//move
enable_movable_ptr(enable_movable_ptr<T>&& p) {};
enable_movable_ptr& operator=(enable_movable_ptr<T>&& p) {};
//destructor
~enable_movable_ptr() {delete ptr_; };
T* get() {return ptr_; };
movable_ptr<T>* getFirst() { return first_; };
void setFirst(movable_ptr<T>* p) { first_ = p; };
private:
T* ptr_ = nullptr;
movable_ptr<T>* first_ = nullptr;
};
template<typename T> class movable_ptr {
public:
//parameterless constructor
movable_ptr() {};
//constructor from T*
movable_ptr(T* p) : ptr_(p) { add_to_tracked(this); };
//copy
movable_ptr(const enable_movable_ptr<T>&) {};
movable_ptr& operator=(const enable_movable_ptr<T>& p) {};
//move
movable_ptr(enable_movable_ptr<T>&& p) {};
movable_ptr& operator=(enable_movable_ptr<T>&& p) {};
//destructor
~movable_ptr() {};
//operators
T& operator*() const { return ptr_; };
T* operator->() const { return ptr_; };
explicit operator bool() const noexcept { return ptr_ != nullptr; };
bool operator!() const { return ptr_ == nullptr; };
bool operator==(const movable_ptr<T>& p) const { return p.get() == ptr_; };
bool operator!=(const movable_ptr<T>& p) const { return p.get() != ptr_; };
//reset
void reset() noexcept { ptr_, next_, prev_ = nullptr; };
template<typename X> void reset(X* p) { remove_from_tracked(this); ptr_ = p; add_to_tracked(this); };
//access to variables
enable_movable_ptr<T>* get() {return ptr_; };
movable_ptr<T>* getNext() { return next_; };
void setNext(movable_ptr<T>* p) { next_ = p; };
movable_ptr<T>* getPrevious() {return prev_; };
void setPrevious(movable_ptr<T>* p) { prev_ = p; };
//get_movable
movable_ptr<T>* get_movable(enable_movable_ptr<T>& p) {};
private:
enable_movable_ptr<T>* ptr_ = nullptr;
movable_ptr<T>* next_ = nullptr;
movable_ptr<T>* prev_ = nullptr;
};
template<typename T> movable_ptr<T> get_movable(enable_movable_ptr<T>& p){
return new movable_ptr<p>(p);
};
//removes movable_ptr from tracked pointers
template<typename T> void remove_from_tracked(movable_ptr<T>* p) {
movable_ptr<T>* first = p->get()->getFirst();
movable_ptr<T>* prev = p->get()->getFirst()->getPrevious();
movable_ptr<T>* next = p->get()->getFirst()->getNext();
if (first == p && next != nullptr)
{
p->get()->setFirst(next);
}
if (prev != next)
{
next->setPrevious(prev);
prev->setNext(next);
}
else
{
next->setPrevious(nullptr);
prev->setNext(nullptr);
}
};
//adds movable_ptr to tracked pointers
template<typename T> void add_to_tracked(movable_ptr<T>* p) {
movable_ptr<T>* first = p->get()->getFirst();
movable_ptr<T>* prev = p->get()->getFirst()->getPrevious();
if (first != nullptr)
{
if (prev != nullptr) {
prev->setNext(p);
}
first->setPrevious(p);
}
p->get()->setFirst(p);
};
#endif
#include <memory>
#include <array>
#include "movable_ptr.hpp"
class MovableNode : public enable_movable_ptr<MovableNode>
{
public:
static const int MAX_REFS = 4;
using RefArray = std::array<movable_ptr<MovableNode>, MAX_REFS>;
MovableNode() : _isValid(false), _value(0), _refs() {}
MovableNode(int value) : _isValid(true), _value(value), _refs() {}
bool isValid() const { return _isValid; }
int value() const { checkValid(); return _value; }
RefArray& refs() { checkValid(); return _refs; }
private:
bool _isValid;
int _value;
RefArray _refs;
void checkValid() const;
};
The trouble is, the compiler is unable to convert the ptr_ variable to the correct return type in operator*. operator== and operator!= are in a similar situation, but this time it is the p.get() that cannot be converted from const movable_ptr<T> to movable_ptr<T> &, so that it can be properly compared.
Errors:
Error C2440 'return': cannot convert from 'enable_movable_ptr<MovableNode> *const ' to 'T &'
Error C2662 'enable_movable_ptr<A> *movable_ptr<T>::get(void)': cannot convert 'this' pointer from 'const movable_ptr<T>' to 'movable_ptr<T> &'
I am pretty sure I am just missing a minor detail, but how would I go about solving this?
Code that instantiates the classes:
#include <iostream>
#include <memory>
#include <string>
#include "movable_ptr.hpp"
using namespace std;
class A : public enable_movable_ptr<A>
{
public:
int val;
A(int val) : val(val) {}
};
void test_ptr_dereference() {
A x(42);
auto px = get_movable(x);
TEST_ASSERT(&*px == &x);
TEST_ASSERT(&px->val == &x.val);
}
int main(int argc, char* argv[]) {
test_ptr_dereference();
}

"double free or corruption" of shared_ptr in binary tree

I'm writing codes about binary tree using smart pointer, but there is something wrong with destructor. I don't know where the momery leaks. But when I remove all components with parent pointer in BinNode, it runs with no errors.
Header file is showed as follow:
#ifndef BINARY_H
#define BINARY_H
#include <memory>
#include <iostream>
#ifndef RANK_DEFINED
#define RANK_DEFINED
typedef int64_t Rank;
#endif
template <typename T> class BinaryTree;
typedef enum {RB_RED, RB_BLACK} RBColor;
template <typename T>
class BinNode {
public:
friend class BinaryTree<T>;
BinNode() = default;
BinNode(const T& data, std::shared_ptr<BinNode> pare = nullptr, std::shared_ptr<BinNode> left = nullptr, std::shared_ptr<BinNode> right = nullptr,
int h = 1) : _data(data), left_child(left), right_child(right), parent(pare), height(h){}
~BinNode() = default;
std::shared_ptr<BinNode> insertAsLeft(const T& e) {
left_child = std::make_shared<BinNode>(e);
left_child->parent = std::shared_ptr<BinNode>(this);
return left_child;
}
std::shared_ptr<BinNode> insertAsRight(const T& e) {
right_child = std::make_shared<BinNode>(e, this);
return right_child;
}
int size() const;
std::shared_ptr<BinNode> succ();
template <typename VST> void travelLevel(VST (*f)());
template <typename VST> void travelPre(VST (*f)());
template <typename VST> void travelIn(VST&);
template <typename VST> void travelPost(VST&);
bool operator<(BinNode const& bn) { return _data < bn._data; }
bool operator==(BinNode const& bn) { return _data == bn._data; }
bool isRoot() { return !(parent); }
bool isLChild() { return !isRoot() && this == parent->left_child; }
bool isRChild() { return !isRoot() && this == parent->right_child; }
bool hasParent() { return !isRoot(); }
bool hasLChild() { return !left_child; }
bool hasRChild() { return !right_child; }
bool hasChild() { return hasLChild() || hasRChild(); }
bool hasBothChild() { return hasLChild() && hasRChild(); }
bool isLeaf() { return !hasChild(); }
std::shared_ptr<BinNode> sibling() const {
return (isLChild() ? parent->right_child : parent->left_child);
}
std::shared_ptr<BinNode> uncle() const {
return parent->sibling();
}
private:
T _data;
std::shared_ptr<BinNode<T>> left_child = nullptr;
std::shared_ptr<BinNode<T>> right_child = nullptr;
std::shared_ptr<BinNode<T>> parent = nullptr;
int height = 1;
};
// Binary Tree Defination
template <typename T>
class BinaryTree
{
using BN = BinNode<T>;
public:
BinaryTree(): _size(0), _root(nullptr) {}
~BinaryTree() = default;
int size() const { return _size; }
bool empty() const { return !_root; }
std::shared_ptr<BN> root() const { return _root; }
std::shared_ptr<BN> insertAsRoot(const T& e);
std::shared_ptr<BN> insertAsLC(std::shared_ptr<BN> pare, const T& e);
std::shared_ptr<BN> insertAsRC(std::shared_ptr<BN> pare, const T& e);
std::shared_ptr<BN> insertAsLC(std::shared_ptr<BN> pare, BinaryTree<T> bt);
std::shared_ptr<BN> insertAsRC(std::shared_ptr<BN> pare, BinaryTree<T> bt);
int remove(std::shared_ptr<BN>);
BinaryTree* secede(std::shared_ptr<BN>);
template <typename VST>
void travelLevel(VST& visit) { if (_root) _root->travelLevel(visit); }
template <typename VST>
void travelPre(VST& visit) { if (_root) _root->travelPre(visit); }
template <typename VST>
void travelIn(VST& visit) { if (_root) _root->travelIn(visit); }
template <typename VST>
void travelPost(VST& visit) { if (_root) _root->travelPost(visit); }
protected:
Rank _size;
std::shared_ptr<BN> _root;
virtual int updateHeight(std::shared_ptr<BN>);
void updateHeightAbove(std::shared_ptr<BN>);
void delTree(std::shared_ptr<BN>);
};
template <typename T>
std::shared_ptr<BinNode<T>> BinaryTree<T>::insertAsRoot(const T& e)
{
_root = std::make_shared<BN>(e);
_size = 1;
return _root;
}
template <typename T>
std::shared_ptr<BinNode<T>> BinaryTree<T>::insertAsLC(std::shared_ptr<BN> pare, const T& e)
{
auto newNode = pare->insertAsLeft(e);
_size++;
updateHeightAbove(newNode);
return pare->left_child;
}
template <typename T>
std::shared_ptr<BinNode<T>> BinaryTree<T>::insertAsRC(std::shared_ptr<BN> pare, const T& e)
{
}
template <typename T>
void BinaryTree<T>::updateHeightAbove(std::shared_ptr<BN> x)
{
while(x)
{
updateHeight(x);
x = x->parent;
}
}
template <typename T>
int BinaryTree<T>::updateHeight(std::shared_ptr<BN> x)
{
Rank lh = 1, rh = 1;
if (x->left_child)
lh = x->left_child->height;
if (x->right_child)
rh = x->right_child->height;
x->height = (lh > rh) ? lh : rh;
return x->height;
}
#endif
and main function is:
int main()
{
BinaryTree<int> bt;
bt.insertAsRoot(1);
bt.insertAsLC(bt.root(), 2);
cout << bt.size() << endl;
return 0;
}
Result is double free or corruption (out).
There are two problems with your parent links:
Both the downwards and the upwards pointers are std::shared_ptr. This is known as a reference cycle and prohibits your tree from ever being destroyed properly. A common solution is to make the parent pointer a std::weak_ptr, such that it does not count towards keeping the parent alive.
The second problem is hidden in your insertAsLeft: std::shared_ptr<BinNode>(this) will construct a new shared_ptr with refcount 1. This means that you have multiple shared_ptrs pointing to the same block of memory and the first one that drops to refcount 0 frees the block, leaving you with dangling pointers. Luckily for you, C++ has a ready-made solution. Simply inherit from std::enable_shared_from_this and use left_child->parent = shared_from_this(); instead. In a nutshell, this construction allows BinNode to keep track of which shared_ptr owns it.

illeagal indirection error in iterator class

Got this error messege on an attempt to return the data from an Item template under iterator class.
I don't understand why Im getting an error (C++)..
template<class E>
class Item {
Item<E>* next;
E data;
public:
Item() :next(NULL) {}
Item(const E& pdata) :next(NULL), data(pdata) {}
void setNext(Item<E>& next) { this->next = next; }
Item<E>& getNext() { return next; }
void setData(const E pdata) { this->data = data; }
E& getData() { return data; }
};
this is the iterator class:
class Iterator {
Item<T>* p;
public:
Iterator(Item<T>* pt = NULL) :p(pt) {}
Iterator& operator++(int) {
p = p->getNext();
return *this;
}
T& operator*() { return *(p->getData()); }
friend class RoundList<T>;
};
they're both under a template class that called RoundList (template )
help...?
This is the code I tested.
template <class E>
class Item {
Item<E>* next;
E data;
public:
Item() :next(NULL) {}
Item(const E& pdata) :next(NULL), data(pdata) {}
void setNext(Item<E>& next) { this->next = next; }
Item<E>& getNext() { return next; }
void setData(const E pdata) { this->data = pdata; }
E& getData() { return data; }
};
template <class T>
class Iterator {
Item<T>* p;
public:
Iterator(Item<T>* pt = NULL) :p(pt) {}
Iterator& operator++(int) {
p = p->getNext();
return *this;
}
T& operator*() { return p->getData(); }
};
int main()
{
Item<int>* test = new Item<int>();
Iterator<int>* iterTest = new Iterator<int>(test);
test->setData(2);
cout << iterTest->operator*() << endl;
cout << (test->getData() = 4) << endl;
}
I'm not sure how you want to use your Iterator, but I tried with a simple type, like int and it works. Anyway I change your *(p->getData()) in p->getData() since in getData you are already returning a reference.

How do I overload an operator for a template class in C++?

I have a binary search tree class (BST.h) and a node class (Node.h) of which works fine when I store data types such as integers in it. My problem is trying store class objects in my BST and use an attribute from the object as the key. My program also has a student class which contains studentID and studentName. How would I write an operator overload in my student class so every time my BST preforms operation on nodes, it will overload to the student.getID(), instead of operating on the object itself. I have the rough idea of what the overload function should look like but i don't know where it should go or if its coded correctly anyway.
//My attempt at an operator overload
bool operator< (const Student &s1, const Student &s2)
{
return s1.GetID < s2.GetID;
}
//Node.h
#ifndef NODE_H
#define NODE_H
#include <iostream>
using namespace std;
template<class T>
class Node
{
public:
Node();
T data;
Node *left;
Node *right;
Node(T);
};
template<class T>
Node<T>::Node()
{
}
template<class T>
Node<T>::Node(T d)
{
data = d;
left = NULL;
right = NULL;
}
#endif //
//BST.h
#ifndef BST_H
#define BST_H
#include <iostream>
#include "Node.h"
#include <string>
using namespace std;
template<class T>
class BST
{
public:
BST();
void Insert(T);
Node<T> *Search(T);
void preOrder();
void inOrder();
void postOrder();
~BST();
private:
Node<T> *root;
void Insert(T , Node<T> *aNode);
Node<T> *Search(T, Node<T> *aNode);
void preOrder(Node<T> *aNode);
void inOrder(Node<T> *aNode);
void postOrder(Node<T> *aNode);
};
template<class T>
BST<T>::BST()
{
root = NULL;
}
template<class T>
void BST<T>::Insert(T data, Node<T> *aNode)
{
if (data < aNode->data)
{
if (aNode->left != NULL)
{
Insert(data, aNode->left);
}
else
{
aNode->left = new Node<T>(data);
aNode->left->left = NULL;
aNode->left->right = NULL;
}
}
else
{
if (data >= aNode->data)
{
if (aNode->right != NULL)
{
Insert(data, aNode->right);
}
else
{
aNode->right = new Node<T>(data);
aNode->right->left = NULL;
aNode->right->right = NULL;
}
}
}
}
template<class T>
void BST<T>::Insert(T data)
{
if (root != NULL)
{
Insert(data, root);
}
else
{
root = new Node<T>(data);
root->left = NULL;
root->right = NULL;
}
}
template<class T>
Node<T>* BST<T>::Search(T data, Node<T> *aNode)
{
if (aNode != NULL)
{
if (data == aNode->data)
{
return aNode;
}
if (data < aNode->data)
{
return Search(data, aNode->left);
}
else
{
return Search(data, aNode->right);
}
}
else
{
return NULL;
}
}
template<class T>
Node<T>* BST<T>::Search(T data)
{
return Search(data, root);
}
template<class T>
void BST<T>::preOrder()
{
preOrder(root);
}
template<class T>
void BST<T>::preOrder(Node<T> *aNode)
{
if (aNode != NULL)
{
cout << aNode->data << " ";
preOrder(aNode->left);
preOrder(aNode->right);
}
}
template<class T>
void BST<T>::inOrder()
{
inOrder(root);
}
template<class T>
void BST<T>::inOrder(Node<T> *aNode)
{
if (aNode != NULL)
{
inOrder(aNode->left);
cout << aNode->data << " ";
inOrder(aNode->right);
}
}
template<class T>
void BST<T>::postOrder()
{
postOrder(root);
}
template<class T>
void BST<T>::postOrder(Node<T> *aNode)
{
if (aNode != NULL)
{
postOrder(aNode->left);
postOrder(aNode->right);
cout << aNode->data << " ";
}
}
template<class T>
BST<T>::~BST()
{
}
#endif // !BST_H
//Student.h
#ifndef STUDENT_H
#define STUDENT_H
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
Student();
Student(string, int);
~Student();
int Student::GetID();
private:
string name;
int ID;
};
inline int Student::GetID()
{
return ID;
}
You seem to be asking about operator< taking Students , however Student is not a class template, so the title of your post is baffling.
As someone else pointed out, your operator< is almost correct, except you have to actually call GetID() instead of comparing pointers to member functions.
This won't work yet until you fix GetID however. Instead of int Student::GetID(); it should be:
int GetID() const;
The const means that it can be called on objects passed by const reference, as you have in your operator< implementation. And you don't repeat the Student:: when declaring functions inside the class. (You use it when defining class members outside of the class definition).
Declare it as a friend function within your Student class, next to the rest of your member functions
friend bool operator < (Student& s1, Student& s2);
Your implementation is correct, it should go outside your Student class within the same header file.

No appropriate default constructor

This is a learning project so please give any additional advice that comes to mind.
I am trying to learn data structures by re-implementing some STL containers/algorithms and I've started with linked lists. If I try to make a list of a class lacking a default constructor I would get a compiler error of "no appropriate default constructor":
#include "list.h"
#include <list>
class testA
{
private:
int mInt;
public:
testA(int i) : mInt(i) {}
};
int _tmain(int argc, _TCHAR* argv[])
{
std::list<testA> wS; // Fine
wS.push_back(1); // Fine
MTL::list<testA> wM; // 'testA' has no appropriate default constructor
wM.push_back(1);
return 0;
}
The problem is I'm using a dummy node in the list to store a link to the beginning and the end of the list. I use this mainly to get the .end() function to work properly and give a decrement-able iterator to one past the end of the list. When I declare an empty list one of the functions wants the default constructor for the templated type so it can make my dummy node! If no default constructor exists, it gives the error message.
On the positive side, it looks like a couple of STL implementations actually use the dummy head node idea. On the negative side, it looks like they pull some nasty tricks to get around this issue. My visual studio 2010 STL implementation eventually calls raw operator new and delete without calling the constructor. I've basically copied what it has (look for ::operator new and delete) and I get it to compile. Here is the full code:
#include <cassert>
#include <iostream>
namespace MTL
{
template< typename T >
class list
{
private:
// If ListNode is in the private part of list, clients
// can't mess around with ListNodes.
template <typename T>
class ListNode
{
private:
ListNode<T>* p_mNextNode;
ListNode<T>* p_mPreviousNode;
T mNodeVal;
public:
// ListNode() :
// p_mNextNode(0),
// p_mPreviousNode(0) {}
ListNode(T const & aVal) :
p_mNextNode(0),
p_mPreviousNode(0),
mNodeVal(aVal) {}
ListNode<T>* GetNextNode() {return p_mNextNode;}
ListNode<T>* GetPreviousNode() {return p_mPreviousNode;}
void SetNextNode(ListNode<T>* const aNode) {p_mNextNode = aNode;}
void SetPreviousNode(ListNode<T>* const aNode) {p_mPreviousNode = aNode;}
T& GetNodeVal() {return mNodeVal;}
};
public:
class iterator
{
private:
ListNode<T>* mIteratorNode;
public:
iterator() : mIteratorNode(0) {}
iterator(ListNode<T>* aListNode) {mIteratorNode = aListNode;}
T& operator*() {return mIteratorNode->GetNodeVal();}
iterator& operator++() {mIteratorNode = mIteratorNode->GetNextNode(); return *this;}
iterator& operator--() {mIteratorNode = mIteratorNode->GetPreviousNode(); return *this;}
bool operator==(iterator const& aIterator) {return mIteratorNode==aIterator.mIteratorNode;}
bool operator!=(iterator const& aIterator) {return !(mIteratorNode==aIterator.mIteratorNode);}
ListNode<T>* GetNode() {return mIteratorNode;}
ListNode<T>* GetNextNode() {return mIteratorNode->GetNextNode();}
ListNode<T>* GetPreviousNode() {return mIteratorNode->GetPreviousNode();}
};
private:
ListNode<T>* p_mHeadNode;
void insert(ListNode<T>* const aNewNode, iterator& aIterator)
{
ListNode<T>* currentNode = aIterator.GetNode();
ListNode<T>* currentsPreviousNode = currentNode->GetPreviousNode();
currentsPreviousNode->SetNextNode(aNewNode);
aNewNode->SetPreviousNode(currentsPreviousNode);
aNewNode->SetNextNode(currentNode);
currentNode->SetPreviousNode(aNewNode);
}
void eraseNode(ListNode<T>* aListNode)
{
ListNode<T>* previousNode = aListNode->GetPreviousNode();
ListNode<T>* nextNode = aListNode->GetNextNode();
previousNode->SetNextNode(aListNode->GetNextNode());
nextNode->SetPreviousNode(aListNode->GetPreviousNode());
if (p_mHeadNode != aListNode)
{
delete aListNode;
}
}
protected:
public:
list() : p_mHeadNode(static_cast<ListNode<T>*>(::operator new (sizeof(ListNode<T>))))
{
// To get .begin or .end to work immediately after construction
p_mHeadNode->SetNextNode(p_mHeadNode);
p_mHeadNode->SetPreviousNode(p_mHeadNode);
}
list(list const& aList) : p_mHeadNode(static_cast<ListNode<T>*>(::operator new (sizeof(ListNode<T>))))
{
p_mHeadNode->SetNextNode(p_mHeadNode);
p_mHeadNode->SetPreviousNode(p_mHeadNode);
ListNode<T>* pCurrent = (aList.p_mHeadNode)->GetNextNode();
while (pCurrent != aList.p_mHeadNode)
{
this->push_back(pCurrent);
pCurrent = pCurrent->GetNextNode();
}
}
void push_front(T const& aNewVal)
{
ListNode<T>* newNode = new ListNode<T>(aNewVal);
this->insert(newNode,this->begin());
}
void push_back(T const& aNewVal)
{
ListNode<T>* newNode = new ListNode<T>(aNewVal);
this->insert(newNode,this->end());
}
void push_back(ListNode<T>* const aListNode)
{
this->push_back(aListNode->GetNodeVal());
}
void push_front(ListNode<T>* const aListNode)
{
this->push_front(aListNode->GetNodeVal());
}
T& front(){ return p_mHeadNode->GetNextNode()->GetNodeVal(); }
T& back(){ return p_mHeadNode->GetPreviousNode()->GetNodeVal(); }
void pop_front() {this->eraseNode(p_mHeadNode->GetNextNode());}
void pop_back() {this->eraseNode(p_mHeadNode->GetPreviousNode());}
const T& front() const { return (p_mHeadNode->GetNextNode())->GetNodeVal(); }
const T& back() const { return (p_mHeadNode->GetPreviousNode())->GetNodeVal(); }
iterator begin() {return iterator(p_mHeadNode->GetNextNode());}
iterator end() {return iterator(p_mHeadNode);}
iterator insert(iterator aPosition, const T& aVal)
{
assert(0);
return iterator();
}
iterator insert(iterator aPosition, unsigned int n, const T& aVal)
{
ListNode<T>* newNode = 0;
for (unsigned int i = 0; i < n; ++i)
{
newNode = new ListNode<T>(aVal);
this->insert(newNode,aPosition);
++aPosition;
}
return iterator(newNode->GetNextNode());
}
iterator insert(iterator aPosition, iterator aFirst, iterator aLast)
{
assert(0);
return iterator();
}
unsigned int size()
{
unsigned int counter = 0;
ListNode<T>* pCurrent = p_mHeadNode->GetNextNode();
while (pCurrent != p_mHeadNode)
{
++counter;
pCurrent = pCurrent->GetNextNode();
}
return counter;
}
~list()
{
this->clear();
::operator delete(p_mHeadNode);
}
void clear()
{
ListNode<T>* pCurrent = p_mHeadNode->GetNextNode();
ListNode<T>* pNext = pCurrent->GetNextNode();
while (pNext != p_mHeadNode)
{
this->eraseNode(pCurrent);
pCurrent = pNext;
pNext = pCurrent->GetNextNode();
}
// All but the last has been deleted
this->eraseNode(pCurrent);
}
bool empty() {return (p_mHeadNode->GetNextNode() != p_mHeadNode);}
friend std::ostream& operator<<(std::ostream& os, list<T> const& aList)
{
ListNode<T>* pCurrent = (aList.p_mHeadNode)->GetNextNode();
std::cout << "List Contents are:\n";
std::cout << "{";
while (pCurrent != aList.p_mHeadNode)
{
std::cout << pCurrent->GetNodeVal();
pCurrent = pCurrent->GetNextNode();
if (pCurrent != aList.p_mHeadNode)
{
std::cout << ",";
}
}
std::cout << "}\n";
return os;
}
};
};
Surely, there must be a cleaner way to get this to work. I can't seem to split my ListNode into a base class of just previous and next pointers and a derived class of the contained data because GetNodeVal() needs a return type of the templated value. So again I would need at least an appropriate constructor to get a dummy value in the base class. I could not make this pure virtual because then my dummy node can not be instantiated as a base class.
This:
http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-3.3/stl__list_8h-source.html
version is using static casts which seem to be less nasty but I haven't been able to apply it to my code.
So, how can I get this code to work without calling raw operator new/deletes? And will it be any cleaner?
One option for delayed/optional construction of the value could be:
template <typename T>
class ListNode
{
private:
ListNode<T>* p_mNextNode;
ListNode<T>* p_mPreviousNode;
union {
char dummy;
T mNodeVal;
};
ListNode() :
p_mNextNode(0),
p_mPreviousNode(0),
dummy() {}
ListNode(T const & aVal) :
p_mNextNode(0),
p_mPreviousNode(0),
mNodeVal(aVal) {}
...
};
You will need to explicitly call its destructor, however, since the compiler no longer knows which of the variant members is active.
But it's better to do this for the dummy ListNode inside the List. I guess that the implementation with ::operator new was also special-casing the dummy. Something like this:
template< typename T >
class List
{
private:
class ListNode
{
private:
ListNode* p_mNextNode;
ListNode* p_mPreviousNode;
T mNodeVal;
};
class DummyListNode
{
public:
ListNode* p_mNextNode;
ListNode* p_mPreviousNode;
};
These are both "standard-layout", and by 9.2:
If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them.
Let's take advantage of that now:
union {
DummyListNode dummy_head_tail;
ListNode head_tail
};
// ... iterators and stuff, using &head_tail as the first and last ListNode*
public:
List() : dummy_head_tail{ nullptr, nullptr } { }
~List() { /* free all nodes, then */ dummy_head_tail->~DummyListNode(); }
};