0

I have a problem with creating base class for DoubleLinkedList.

Right now it's giving me this error

/tmp/cc3lORia.o:(.rodata._ZTV24AbstractDoubleLinkedListIiE[_ZTV24AbstractDoubleLinkedListIiE]+0x10): undefined reference to `AbstractDoubleLinkedList::createNewNode(int)' collect2: error: ld returned 1 exit status

I've tried this and that as you can see by commented lines in code, but none of it works.

So how to define abstract template class with abstract method (factory method by the way) and then redefine it in children classes?

/*
 * AbstractDoubleLinkedList.hpp
 *
 *  Created on: Mar 2, 2015
 *      Author: michael
 */

#ifndef ABSTRACTDOUBLELINKEDLIST_H_
#define ABSTRACTDOUBLELINKEDLIST_H_

#include <vector>

using namespace std;


template <class T> class ListNode {

private:
    void init();
public:
    ListNode();
    ListNode(T value);
    ListNode *previous;
    ListNode *next;
    T value;

};

template <class T> void ListNode<T>::init() {
    previous = nullptr;
    next = nullptr;
}

template <class T> ListNode<T>::ListNode() {
    init();
}

template <class T> ListNode<T>::ListNode(T value) {
    init();
    this->value = value;
}

template <class T> class AbstractDoubleLinkedList {

private:

    void pullOutNode(ListNode<T> *node);

protected:

    virtual ListNode<T>* createNewNode(T element);

public:
    AbstractDoubleLinkedList();
    void push_back(T element);
    T front();
    T back();
    void insertBefore(ListNode<T> *node, ListNode<T> *beforeNode);
    void insertAfter(ListNode<T> *node, ListNode<T> *afterNode);
    void moveNodeAfter(ListNode<T> *node, ListNode<T> *afterNode);
    vector<T> toVector();
    ListNode<T> *frontNode;
    ListNode<T> *backNode;
};


template <class T> void AbstractDoubleLinkedList<T>::push_back(T element) {
    ListNode<T>* node = createNewNode(element);
    node->previous = backNode;
    if (backNode != nullptr) {
        backNode->next = node;
        node->previous = backNode;
    }
    else {
        frontNode = node;
    }
    backNode = node;
}

template <class T> void AbstractDoubleLinkedList<T>::pullOutNode(ListNode<T> *node) {
    if (node != frontNode) {
        node->previous->next = node->next;
    }
    else {
        frontNode = node->next;
    }
    if (node != backNode) {
        node->next->previous = node->previous;
    }
    else {
        backNode = node->previous;
    }
}

template <class T> T AbstractDoubleLinkedList<T>::front() {
    return frontNode->value;
}

template <class T> T AbstractDoubleLinkedList<T>::back() {
    return backNode->value;
}

template <class T> void AbstractDoubleLinkedList<T>::insertAfter(ListNode<T> *node, ListNode<T> *afterNode) {
    node->previous = afterNode;
    node->next = afterNode->next;
    afterNode->next = node;
    if (afterNode == backNode) {
        backNode = node;
    }
}

template <class T> void AbstractDoubleLinkedList<T>::insertBefore(ListNode<T> *node, ListNode<T> *beforeNode) {
    node->next = beforeNode;
    beforeNode->previous->next = node;
    beforeNode->previous = node;
    if (beforeNode == frontNode) {
        frontNode = node;
    }
}

template <class T> void AbstractDoubleLinkedList<T>::moveNodeAfter(ListNode<T> *node, ListNode<T> *afterNode) {
    pullOutNode(node);
    node->previous = afterNode;
    node->next = afterNode->next;
    if (node->next == nullptr) {
        backNode = node;
    }
    afterNode->next = node;

}


template <class T> vector<T> AbstractDoubleLinkedList<T>::toVector() {
    ListNode<T>* node = frontNode;
    vector<int> listAsVector;
    bool shouldHaveAnother = (frontNode != nullptr);
    while(shouldHaveAnother) {
        listAsVector.push_back(node->value);
        if (node->next != nullptr)
            node = node->next;
        else {
            shouldHaveAnother = false;
        }
    }
    return listAsVector;
}


template <class T>  AbstractDoubleLinkedList<T>::AbstractDoubleLinkedList() {
    frontNode = nullptr;
    backNode = nullptr;
}


#endif /* ABSTRACTDOUBLELINKEDLIST_HPP_ */



/*
 * DoubleLinkedList.hpp
 *
 *  Created on: Feb 26, 2015
 *      Author: michael
 */

#ifndef DOUBLELINKEDLIST_HPP_
#define DOUBLELINKEDLIST_HPP_

#include "AbstractDoubleLinkedList.hpp"

template <class T> class DoubleLinkedList : public AbstractDoubleLinkedList<T> {

protected:
    ListNode<T>* createNewNode(T element) {
            return new ListNode<T>(element);

        }
public:
    ~DoubleLinkedList() {
        ListNode<T>* node = this->backNode;
        bool shouldHaveAnother = (node != nullptr);
        while(shouldHaveAnother) {
            ListNode<T>* ptr = node->previous;
            delete node;
            if (ptr != nullptr)
                node = ptr;
            else {
                shouldHaveAnother = false;
            }
        }
    };



};

//template <class T> ListNode<T> DoubleLinkedList<T>::createNewNode(T element) {
//  return new ListNode<T>(element);
//}

//template <class T> DoubleLinkedList<T>::~DoubleLinkedList() {
//
//  ListNode<T>* node = this->backNode;
//  bool shouldHaveAnother = (node != nullptr);
//  while(shouldHaveAnother) {
//      ListNode<T>* ptr = node->previous;
//      delete node;
//      if (ptr != nullptr)
//          node = ptr;
//      else {
//          shouldHaveAnother = false;
//      }
//  }
//}


#endif /* DOUBLELINKEDLIST_HPP_ */

EDIT1: So, there is a question "Why do I need my own container class and why not use list or vector?"

I need a data structure with constant time random access and constant time deletion and insertion. BUT I've already tried std::unordered_set and It's not good enough (while technically it meets the requirements) because of allocations and deallocations of memory (when deleting and inserting)

So I figured another way. I want to use a linked list with one "guard" element and the end. When I need to "delete" element from it I would move it after guard. And to check if list is "empty" I would check if the first element is the guard element. But I need a constant-time random access. This can be achieved by map of pointers to every element.

But to achieve maximum performance I need to also minimize cache misses. And I thinks that std::list would be scattered across the memory, because it is the normal behaviour for it. So I figured that the only way to do so - is to allocate vector> and then use this preallocated nodes to new elements.

So am I wrong somewhere? Can I achieve maximum performance more easier?

user1685095
  • 5,787
  • 9
  • 51
  • 100
  • 2
    You haven't defined it (in the base class), if you want it to be virtual pure, append a `= 0` to its declaration. – imreal Mar 02 '15 at 15:08
  • 1
    You do realise that using OOP for generic container classes is a bad idea, don't you? This is not Java. And of course, in real code you'd use what the language already offers (i,e, `std::list`). – Christian Hackl Mar 02 '15 at 16:02
  • @ChristianHackl, I don't realise why OOP for generic container is bad idea, enligten me, please. I'm not very familiar with C++. I'm writing the real code. And one of the demands is that nodes of linked list would be contiguous in memory, so that cache misses would be minified. Can I do this with std::list? Would that be easier, than implementing linked list myself? – user1685095 Mar 02 '15 at 20:16
  • @imreal, Ok. Thanks for your help. I've added = 0 to abstract methods in base class. Now I have this error. Undefined symbols for architecture x86_64: "AbstractDoubleLinkedList::~AbstractDoubleLinkedList()", referenced from: DoubleLinkedList::~DoubleLinkedList() – user1685095 Mar 02 '15 at 20:25
  • When using polymorphism always define a virtual destructor. I may be empty `virtual AbstractDoubleLinkedList() {}` – imreal Mar 02 '15 at 20:34
  • @user1685095: Contiguous memory would be guaranteed by std::vector. I find it unusual to combine the requirements for contiguous memory with those of linked lists. In any cause, my point was that OOP seems unnecessary here. When would you choose a different implementation at runtime? – Christian Hackl Mar 03 '15 at 05:52
  • @ChristianHackl Probably I would use the version with preallocated nodes, which I haven't shown here. But I can use both of them in my performance tests. But why do you think OOP is not good in template classes? – user1685095 Mar 03 '15 at 07:53
  • @user1685095: Because they do not need to be OOP. Look at standard container classes. It's a completely different philosophy than, say, Java. A much more worthwhile goal in C++ would be to make your container compatible with standard algorithms like std::find. – Christian Hackl Mar 03 '15 at 08:36
  • Look, I know what and why I need what I need. So that's for me to decide whether I need OOP or not =). I've already looked at standard container classes. They can't do what I need (or so it seems). My goal - is performance. I'm trying to minimize memory allocations and deallocations, because they degrade performance of my algorithm. I don't care about compatibility with iterators and standard algorithms. I have a very precise set of requirements, and compatibility with stl is not one one them? I'll edit my question with my requirements. – user1685095 Mar 03 '15 at 08:42
  • @ChristianHackl I've edited my post. Maybe this would clear why I need what I need. – user1685095 Mar 03 '15 at 08:53
  • @user1685095 I'm not sure if you are aware, but if you don't need to preserve ordering in your container you can perform deletions in `std::vector` by swapping the element to be deleted with the last element and erasing it. This will have constant time (e.g. no reallocation). – pmr Mar 03 '15 at 09:12
  • 1. Wouldn't erase deallocate memory? 2. I'll give an example. suppose i have a list [1, 2, 3, 4, 5, 7, -1] where -1 is the guard. Now I need to "delete" element 1 3 5. And at the next iteration I need to start from any of the the remaining elements. I just don't see how swapping elements with guard or last element could help with that. I've already thought about that. – user1685095 Mar 03 '15 at 09:19

0 Answers0