Below is queue example in "C++ concurrency in action 2nd".
This example is single threaded queue, which will be modified to explain thread safe.
template<typename T>
class queue {
private:
struct node {
T data;
std::unique_ptr<node> next;
node(T data_) : data(std::move(data_)) {}
};
std::unique_ptr<node> head;
node* tail;
public:
queue() : tail(nullptr) {};
queue(const queue& other) = delete;
queue& operator=(const queue& other) = delete;
std::shared_ptr<T> try_pop() {
if (!head) return std::shared_ptr<T>();
std::shared_ptr<T> const res(std::make_shared<T>(std::move(head->data)));
std::unique_ptr<node> const old_head = std::move(head);
head = std::move(old_head->next);
if(!head) tail = nullptr;
return res;
}
void push(T new_value) {
std::unique_ptr<node> p(new node(std::move(new_value)));
node* const new_tail = p.get();
if(tail) {
tail->next = std::move(p);
} else {
head = std::move(p);
}
tail = new_tail;
}
};
I can understand this queue works fine, but the way try_pop() is implemented seems to be weird.
Is it better way to do like below ?
std::shared_ptr<T> try_pop() {
if (!head) return std::shared_ptr<T>();
std::shared_ptr<T> const res(std::make_shared<T>(std::move(head->data)));
// std::unique_ptr<node> const old_head = std::move(head);
// head = std::move(old_head->next);
head = std::move(head->next);
if(!head) tail = nullptr;
return res;
}
Why head should be copied to old_head on this?
Is there any reason I missed?
Related
I'm struggling with OOP again. I tried to implement signgly linked list, here's the code:
template <typename T>
class node
{
T value;
node* next;
public:
node(const T& n)
{
this->value = n;
this->next = nullptr;
}
~node()
{
//is it ok to leave destructor empty in my case?
}
};
template <typename T>
class list
{
node<T>* head;
node<T>* tail;
public:
list()
{
this->head = nullptr;
this->tail = nullptr;
}
list(const list& arr)
{
*this = list(); //<================== the line I mentioned
node* current_this = this->head;
node* current_arr = this->arr;
while (current_arr != nullptr)
{
current_this = new node(current_arr);
current_arr = current_arr->next;
current_this = current_this->next;
}
}
list(list&& arr)
{
this->head = arr.head;
this->tail = arr.tail;
arr = list(); //<================== the line I mentioned
}
~list()
{
node* current = this->head;
while (current != nullptr)
{
node* next = current->next;
delete current;
current = next;
}
}
};
I wrote some lines without thinking, i.e. arr = list() and *this = list(). I didn't implemented operator= so there should be some troubles. I thought compiler has decided to generate copy/move assigment for me, so I decided to add these lines, just for curiosity sake:
list& operator=(const list& arr) = delete;
list& operator=(list&& arr) = delete;
But it still compiled and I don't understand why. What's the deal here? Shoudn't assignment be forbidden and therefore arr = list() and *this = list() be illegal?
Templates aren't evaluated unless/until you instantiate them. If you don't use the copy or move constructor, then their bugs don't produce errors.
struct Node {
int data;
Node* next;
Node* prev;
Node() : data(0), next(nullptr), prev(nullptr) { }
explicit Node(int elt) : data(elt), next(nullptr), prev(nullptr) { }
};
class DequeEmptyException { };
class Deque {
public:
Deque();
~Deque();
Deque(const Deque& orig);
Deque& operator=(const Deque& rhs);
private:
Node* head;
Node* tail;
int length;
void copy(const Deque& source, Deque& destination);
};
here are is my copy constructor from source to destination.
void Deque::copy(const Deque& source, Deque& destination) {
// create dummy header
destination.head = new Node;
destination.head->next = destination.head->prev = destination.head;
// copy nodes
for (Node *p = source.head->next; p != source.head; p = p->next) {
Node *t = new Node;
t->data = p->data;
// insert at end of list
t->next = destination.head;
t->prev = destination.head->prev;
t->prev->next = t;
t->next->prev = t;
}
source.length = destination.length;
}
This is my attempt. Does it look correct as there are many other implementations within the Deque that I was unable to show. This was the most confusing.
Whenever I implement the front() method (to return the first element of the doubly linked list) in the main I get a segmentation fault even though the back() method (returning the info of tail) that was implemented in a similar manner works. Can someone help?
template <class T>
class Node {
public:
T info;
Node<T>* next;
Node<T>* prev;
Node(const T info){
this->info = info;
next = NULL;
prev = NULL;
}
Node* getNode(T info){
Node* newnode = (Node *)malloc(sizeof(Node));
}
};
template <class T>
class DLlist {
private:
Node<T>* head;
Node<T>* tail;
int size;
public:
DLlist();
T front();
T back();
};
template<class T>
DLlist<T>::DLlist(){
head = NULL;
tail = NULL;
size = 0;
}
template <class T>
void DLlist<T>::addback(const T newdata){
if (isEmpty()){
Node<T> *newnode = new Node<T>(newdata);
head = tail = newnode;
size++;
return;
}
Node<T> *newnode;
newnode = new Node<T>(newdata);
newnode->prev = tail;
newnode->next = NULL;
tail->prev = newnode;
tail = newnode;
size++;
}
template <class T>
T DLlist<T>::front(){
return (head->info);
}
In your addback() function:
Node<T> *newnode; // Two lines where
newnode = new Node<T>(newdata); // one suffices
newnode->prev = tail;
newnode->next = NULL; // Unnecessary, your constructor did this
tail->prev = newnode; // THIS IS YOUR PROBLEM
tail = newnode;
size++;
Your tail should be setting its next pointer to the new node, not its previous. Drawing this stuff out on a piece of paper can go a long way in better understanding how it should work.
I am always willing to chalk up poor formatting on this site to copy/paste, but there are other things you can do to simplify your code, make it a bit more modern, etc.
So here's your code again, cleaned up a tad (This code went through clang-format using the Webkit style):
#include <iostream>
template <class T>
struct Node {
T info;
Node<T>* next = nullptr;
Node<T>* prev = nullptr;
Node(const T& info)
: info(info)
{
}
// Don't know why you need this, so just deleting it because it's a bad
// function
};
template <class T>
class DLlist {
private:
Node<T>* head = nullptr;
Node<T>* tail = nullptr;
int size = 0;
public:
DLlist() = default;
bool isEmpty() { return head == nullptr && tail == nullptr; }
void push_back(const T& newdata);
const T front() const;
const T back() const;
};
template <class T>
void DLlist<T>::push_back(const T& newdata)
{
if (isEmpty()) {
head = new Node<T>(newdata);
tail = head;
size++;
return;
}
Node<T>* newnode = new Node<T>(newdata);
newnode->prev = tail;
tail->next = newnode; // THIS WAS PROBABLY YOUR ISSUE
tail = newnode;
size++;
}
template <class T>
const T DLlist<T>::front() const
{
return head->info;
}
template <class T>
const T DLlist<T>::back() const
{
return tail->info;
}
int main()
{
DLlist<int> list;
list.push_back(42);
list.push_back(54);
std::cout << list.front() << '\n'; // 42 prints just fine, Debian w/ LLVM 9
}
I was trying to implement a linked list for solving an algorithm problem.
It basically worked, however, it turned out that I was using too much memory.
I would appreciate if someone point out defects of following destructor design.
template<typename T>
struct Node {
Node(): item(0),next(0) {}
Node(T x): item(x),next(0) {}
T item;
Node* next;
};
template <typename T>
struct List {
List() : head(0),tail(0) {}
Node<T>* head;
Node<T>* tail;
void insert(T x) {
Node<T>* newNode = new Node<T>(x);
if(head == NULL) {
head = tail = newNode;
} else {
tail->next = newNode;
tail = tail->next;
}
}
void clearRecur(Node<T>* h) {
if(h) {
clearRecur(h->next);
delete h;
}
}
void clear() {
if(head) {
clearRecur(head);
}
}
};
A list can be cleared recursively or iteratively.
Alternatively to your (IMHO correct) version, I use a slight different approach – make the Node itself "responsible" to delete its tail. This leads to recursive clearing as well (but with less code).
Recursive clearing:
template<typename T>
struct Node {
Node(): item(), next(nullptr) {}
Node(T x): item(x), next(nullptr) {}
~Node() { delete next; } // <== recursive clearing
T item;
Node* next;
// Using the default copy ctor would corrupt the memory management.
// Deleting it lets the compiler check for accidental usage.
Node(const Node&) = delete;
// Deleting assignment operator as well.
Node& operator=(const Node&) = delete;
};
template <typename T>
struct List {
List() : head(nullptr), tail(nullptr) {}
~List() { clear(); }
Node<T>* head, tail;
void insert(T x) {
Node<T>* newNode = new Node<T>(x);
if (head == nullptr) head = tail = newNode;
else {
tail->next = newNode;
tail = tail->next;
}
}
void clear() {
delete head;
head = tail = nullptr;
}
// Using the default copy ctor would corrupt the memory management.
// Deleting it lets the compiler check for accidental usage.
List(const List&) = delete;
// Delete assignment operator as well.
List& operator=(const List&) = delete;
};
This is the way, I did it in our current project. At the first glance, it seemed enjoying simple and worked fine. I changed my mind when our beta-testers came into play. In real world projects, the lists were such long that the recursive clearing ran out of stack memory. (Yepp – a stack overflow.) I should've known better!
Thus, I made the clearing iteratively – whereby the "responsibility" is moved back from Node to List. (The API user will not note this as it happens "under the hood".)
Iterative clearing:
template<typename T>
struct Node {
Node(): item(), next(nullptr) {}
Node(T x): item(x), next(nullptr) {}
T item;
Node* next;
};
template <typename T>
struct List {
List() : head(nullptr), tail(nullptr) {}
~List() { clear(); }
Node<T>* head, tail;
void insert(T x) {
Node<T>* newNode = new Node<T>(x);
if (head == nullptr) head = tail = newNode;
else {
tail->next = newNode;
tail = tail->next;
}
}
void clear() {
while (head) {
Node<T> *p = head; head = head->next;
delete p;
}
tail = nullptr;
}
// Using the default copy ctor would corrupt the memory management.
// Deleting it lets the compiler check for accidental usage.
List(const List&) = delete;
// Delete assignment operator as well.
List& operator=(const List&) = delete;
};
I'm trying to compile the example listing_6.6.cpp from the book "C++ Concurrency In Action" of Anthony Williams, but it's not compiling and I don't understand why.
I'm new to multithreading, but I've checked the code and googled for an answer, but I still don't understand why it doesn't compile.
The problem is in this method:
std::unique_ptr<node> pop_head()
{
std::lock_guard<std::mutex> head_lock(head_mutex);
if (head.get() == get_tail())
{
return nullptr;
}
std::unique_ptr<node> const old_head = std::move(head);
head = std::move(old_head->next);
return old_head; // <-- here is the problem
}
the error message is: attempting to reference a deleted function
The full error message is (I'm using VS 2013):
threadsafe_queue.h(34): error C2280: 'std::unique_ptr<threadsafe_queue<Message>::node,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' : attempting to reference a deleted function
and the full code is:
#include <memory>
#include <mutex>
template<typename T>
class threadsafe_queue
{
private:
struct node
{
std::shared_ptr<T> data;
std::unique_ptr<node> next;
};
std::mutex head_mutex;
std::unique_ptr<node> head;
std::mutex tail_mutex;
node* tail;
node* get_tail()
{
std::lock_guard<std::mutex> tail_lock(tail_mutex);
return tail;
}
std::unique_ptr<node> pop_head()
{
std::lock_guard<std::mutex> head_lock(head_mutex);
if (head.get() == get_tail())
{
return nullptr;
}
std::unique_ptr<node> const old_head = std::move(head);
head = std::move(old_head->next);
return old_head;
}
public:
threadsafe_queue() :
head(new node), tail(head.get())
{}
threadsafe_queue(const threadsafe_queue& other) = delete;
threadsafe_queue& operator=(const threadsafe_queue& other) = delete;
std::shared_ptr<T> try_pop()
{
std::unique_ptr<node> old_head = pop_head();
return old_head ? old_head->data : std::shared_ptr<T>();
}
void push(T new_value)
{
std::shared_ptr<T> new_data(
std::make_shared<T>(std::move(new_value)));
std::unique_ptr<node> p(new node);
node* const new_tail = p.get();
std::lock_guard<std::mutex> tail_lock(tail_mutex);
tail->data = new_data;
tail->next = std::move(p);
tail = new_tail;
}
};
Thansk!
TLDR: You can't move from a const object, even when returning.
The constructor selected to initialize the return value of pop_head from a const unique_ptr is the deleted copy constructor, so the compiler is correct to reject this program as ill-formed. If you look at Listing 6.6 in the book itself (page 161-162) you'll notice that the offending const is not present.