C ++: ошибка компоновщика: неопределенная ссылка только на одного конкретного члена класса, определенного в отдельном файле - PullRequest
0 голосов
/ 06 декабря 2011

Я получаю печально известную ошибку «неопределенная ссылка» при попытке скомпилировать и связать несколько файлов и был бы рад, если бы вы могли помочь.

Точное сообщение об ошибке:

g ++ -o main list.cpp main.cpp /tmp/ccv6M2I6.o: в функции main': main.cpp:(.text+0x219): undefined reference to List :: print () const '

main.cpp:

#include <iostream>
#include "list.hpp"
using namespace std;


void printStats(IntList& l) {
    cout << endl << "____________________" << endl;
    cout << "Length: " << l.getCount() << endl;
    cout << "Min: " << l.min() << endl;
    cout << "Max: " << l.max() << endl;
    cout << "Average: " << l.average();
    cout << endl << "____________________" << endl;
}

int main() {
    IntList l = IntList();

    for(int i=1; i <= 10; i++) {
        l.insert(i, l.getCount() - 1); // works fine
    }

    printStats(l); // works fine, too
    l.print(); // causes the error

    return 0;
}

Самое смешное: ни функция-член insert (), ни min (), max () или average () не вызывают проблем.Это просто print ().

[EDIT]: Это не просто print (), но и remove ().

list.hpp:

#ifndef __LIST_HPP__
#define __LIST_HPP__

template <typename T>
class List {
    public:
        class OutOfBoundsException { };

        List();
        List(const List& l);
        ~List();

        List& operator=(const List& l);

        unsigned int getCount() const;
        bool isEmpty() const;
        void print() const;
        void insert(T value, unsigned int position = 0);
        void remove(unsigned int position);
        T pop(unsigned int position = 0);
        T getElement(unsigned int position) const;

    protected:
        // double linked list
        struct dllist_entry {
            T value;
            dllist_entry* next;
            dllist_entry* prev;
        };

        dllist_entry* first;
        dllist_entry* last;

        unsigned int length;

        void clear();

        dllist_entry* getElementRaw(unsigned int position) const;
};

class IntList : public List<int> {
    public:
        IntList();
        IntList(const IntList& l);
        ~IntList();

        IntList& operator=(const IntList& l);

        int max() const;
        int min() const;
        float average() const;
};


#endif

list.cpp:

#include <iostream>
#include "list.hpp"
using namespace std;


template <typename T>
List<T>::List() {
    this->first = NULL;
    this->last = NULL;
    this->length = 0;
}

template <typename T>
List<T>::List(const List& l) {
    this->first = NULL;
    this->last = NULL;
    this->length = 0;

    for(unsigned int i=0; i < l.getCount(); i++) {
        insert(l.getElement(i));
    }
}

template <typename T>
List<T>& List<T>::operator=(const List<T>& l) {
    if(this != &l) {    
        // Liste leeren
        clear();

        for(unsigned int i=0; i < l.getCount(); i++) {
            insert(l.getElement(i));
        }
    }

    return *this;
}

template <typename T>
List<T>::~List() {
    clear();
}

template <typename T>
void List<T>::clear() {
    dllist_entry* iter = first;
    dllist_entry* next;

    while(iter != NULL) {
        next = iter->next;
        delete iter;
        iter = next;
    }

    length = 0;
}

template <typename T>
unsigned int List<T>::getCount() const {
    return this->length;
}

template <typename T>
bool List<T>::isEmpty() const {
    return this->length == 0;
}

template <typename T>
void List<T>::print() const {
    // aus Performance-Gründen nicht getElement() benutzen

    for(dllist_entry* iter = first; iter != NULL; iter = iter->next) {
        cout << iter->value << endl;
    }
}

template <typename T>
void List<T>::insert(T value, unsigned int position) {

    dllist_entry* new_one = new dllist_entry;
    new_one->value = value;

    if(getCount() > 0) {
        if(position < getCount()) {
            if(position == 0) {
                new_one->prev = NULL;
                new_one->next = first;
                first->prev = new_one;
                first = new_one;
            }
            // position > 0
            else {
                dllist_entry* elem = getElementRaw(position);
                new_one->next = elem;
                new_one->prev = elem->prev;
                elem->prev->next = new_one;
                elem->prev = new_one;
            }
        }
        else if(position == getCount()) {
                new_one->next = NULL;
            new_one->prev = last;
            last->next = new_one;
            last = new_one;
        }
        else {
            throw OutOfBoundsException();
        }
    }
    else {
        new_one->next = NULL;
        new_one->prev = NULL;
        first = new_one;
        last = new_one;
    }

    length++;
}    

template <typename T>
T List<T>::pop(unsigned int position) {
    T value = getElement(position);
    remove(position);
    return value;
}

template <typename T>
void List<T>::remove(unsigned int position) {
    dllist_entry* elem = getElementRaw(position);


    if(getCount() == 1) { // entspricht elem == first && elem == last
        first = NULL;
        last = NULL;
    }
    else if(elem == first) {
        elem->next->prev = NULL;
        first = elem->next;
    }
    else if(elem == last) {
        elem->prev->next = NULL;
        last = elem->prev;
    }
    // Element liegt zwischen Anfang und Ende
    // (Wäre das nicht so, hätte getElementRaw() bereits protestiert.)
    else {
        elem->prev->next = elem->next;
        elem->next->prev = elem->prev;
    }

    delete elem;
    length--;
}

template <typename T>
T List<T>::getElement(unsigned int position) const {
    return getElementRaw(position)->value;
}

template <typename T>
typename List<T>::dllist_entry* List<T>::getElementRaw(unsigned int position) const {
    // schließt den Fall getCount() == 0 mit ein
    if(position < getCount()) {
        dllist_entry* iter;

        // aus Performance-Gründen mit der Suche entweder von vorne oder 
        // von hinten beginnen
        if(position <= (getCount() - 1) / 2) {
            iter = first;

            for(unsigned int i=0; i < position; i++) {
                iter = iter->next;
            }
        }
        else {
            iter = last;

            for(unsigned int i = getCount() - 1 ; i > position; i--) {
                iter = iter->prev;
            }
        }

        return iter;
    }
    else {
        throw OutOfBoundsException();
    }
}





IntList::IntList() : List<int>() { }
IntList::IntList(const IntList& l) : List<int>(l) { }
IntList::~IntList() { }

IntList& IntList::operator=(const IntList& l) {
    List<int>::operator=(l);
    return *this;
}


int IntList::min() const {
    // erstes Element separat holen, damit OutOfBoundsException geworfen werden
    // kann, wenn Liste leer ist
    int min = getElement(0);

    for(unsigned int i=1; i < getCount(); i++) {
        int value = getElement(i);
        if(value < min) {
            min = value;
        }
    }

    return min;
}

int IntList::max() const {
    // erstes Element separat holen, damit OutOfBoundsException geworfen werden
    // kann, wenn Liste leer ist
    int max = getElement(0);

    for(unsigned int i=1; i < getCount(); i++) {
        int value = getElement(i);
        if(value > max) {
            max = value;
        }
    }

    return max;
}

float IntList::average() const {
    if(getCount() > 0) {
        int sum = 0;

        for(unsigned int i=0; i < getCount(); i++) {
            sum += getElement(i);
        }

        return (float) sum / getCount();
    }
    else {
        return 0;
    }
}

Извините за большой источник, но я боялся, что мог случайно что-то пропустить, если бы я только опубликовал выдержки.

Для записи: я получил похожее сообщение об ошибке - этовремя с List :: ~ List () - прежде чем я явно объявил / определил деструктор ~ IntList () в list.hpp / list.cpp.Я действительно ожидал, что мне даже не нужно объявлять его, так как деструктор родительского класса List вызывается в любом случае при уничтожении объекта IntList?Кроме того, даже определение деструктора непосредственно в заголовочном файле list.hpp как «~ IntList () {}» не помогло - сообщение об ошибке не исчезло бы, пока я не переместил определение dtor в list.cpp.

С другой стороны: все это прекрасно скомпилировано, когда я все еще находился в одном большом файле.

Спасибо, что нашли время выследить эту ошибку!:)

Ответы [ 2 ]

4 голосов
/ 06 декабря 2011

Определение шаблона должно быть в том же файле, в котором предоставляется объявление.Они не могут быть разделены.

Так что вы либо перемещаете все определения list.cpp в list.hpp, либо делаете это в list.hpp

#ifndef __LIST_HPP__
#define __LIST_HPP__

template <typename T>
class List {

//...

};

class IntList : public List<int> {

//...

};

#include "list.cpp"  //<----------------- do this

#endif

И удаляете строку #include list.hppиз list.cpp файла.Это делает это круглым:

#include <iostream>
//#include "list.hpp" //remove this
using namespace std; //<----- don't do this either - see the note below!


template <typename T>
List<T>::List() {
    this->first = NULL;
    this->last = NULL;
    this->length = 0;
}
//....

Как примечание стороны, сделайте использование using namespace std.Используйте полное имя, например:

std::vector<int> v;
std::sort(...);
//etc
1 голос
/ 06 декабря 2011

Шаблонный код должен находиться в заголовочных файлах.

Шаблоны - это код, сгенерированный компилятором, который генерируется «по требованию», когда вы используете его в своем коде.

Если его нет в заголовкефайл, он не может найти правильный код и сгенерировать его для определенного типа.

То, что вы сделали, создали класс, который наследует от List<int>, а затем скрыли функции-члены с вашей собственной реализацией..

Но вы не реализовали print(), и поскольку источник шаблона не включен в файл, код для List<int>.print() не может быть сгенерирован.

Редактировать:

Просто чтобы выяснить, почему только print() выдал ошибку:

В вашей функции main вы используете 3 функции: getCount() insert()и print()

Теперь давайте посмотрим на вашу реализацию List<int>:

В конструкторе копирования вы вызываете List(const List& l) ....

IntList::IntList(const IntList& l) : List<int>(l) { }

Этот конструктор вызывает insert(), getCount() и getElement():

    for(unsigned int i=0; i < l.getCount(); i++) {
    insert(l.getElement(i));
}

Таким образом, все эти функции создаютсяru класс IntList скомпилирован.
Реализация IntList "видит" реализации шаблона, поэтому эти функции создаются.

С другой стороны, print<int>() вызывается только для первоговремя в функции main, которая не "видит" реализацию шаблона.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...