C++:链接器错误:未定义的引用仅指向在单独文件中定义的一个特定类成员

C++: Linker error: undefined reference only to one specific class member defined in a separate file

本文关键字:定义 一个 成员 单独 未定义 错误 链接 引用 C++ 文件      更新时间:2023-10-16

我在尝试编译&链接几个文件,如果你能提供帮助,我会很高兴。

确切的错误信息:

g++-o主列表.cpp主.cpp/tmp/ccv6M2I6.o:在函数main': main.cpp:(.text+0x219): undefined reference to列表中::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((都不会引起任何问题。这只是打印((。

[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.hpp/List.cpp中的析构函数~IntList((之前,我收到了一条类似的错误消息——这次是List::~List((。我实际上希望我甚至不需要声明它,因为在销毁IntList对象时,父类List的析构因子无论如何都会被调用?此外,即使直接在头文件list.hpp中将析构函数定义为"~IntList(({}"也无济于事——直到我将dtor定义移动到list.cpp,错误消息才会消失。

顺便说一句:当我把它放在一个大文件里的时候,整件事汇编得很好。

感谢您花时间查找此错误!:(

模板的定义应该在提供声明的同一文件中。它们无法分离。

因此,要么将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

并从list.cpp文件中删除行#include list.hpp。它正在使其循环:

#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

模板化的代码需要在头文件中。

模板是编译器生成的代码,当你在代码中使用它时,它会"按需"生成。

如果它不在头文件中,它就无法找到合适的代码并为特定类型生成代码。

您所做的是创建一个从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));
}

因此,所有这些函数都是在编译类IntList时创建的
IntList的实现"看到"了模板实现,因此创建了这些函数。

另一方面,print<int>()只是在main函数中第一次被调用,它没有"看到"模板实现。