使用vscode编译C++多文件(包含模板类)|解决Undefined reference to报错

遇到的问题

今天复现“Algorithms, Part1“课程里“Bags, Queues, And Stacks”里的内容时,想打算顺手重新熟悉了解下C++的一些特性,其中就包括模板类(template)[想要使用它来实现泛型的效果]和头文件(.h)与源文件(.cpp)分离。

我在头文件中定义类,同时使用模板类方便支持多种数据类型,并在源文件中将方法补充完整。大致代码如下所示:(默认命名空间声明最好不要放在头文件,错误示范…)

/* Bag.h */
#pragma once

#include "Node.h"
#include "Node.cpp"

using namespace std;

/* 
 * Bag data structure, no specific store way, here we use linked-list to store the data.
 * It has only add() method, no remove() method.
 * The element in bag has no specific order.
 */
template <class T>
class Bag
{
private:
    Node<T>* _head;
    int _size;
public:
    Bag();
    ~Bag();

    /* Insert in the head, no need to traverse, time complexity: O(1) */ 
    void add(T val);

    /* Return whether the bag is empty or not */
    bool isEmpty();

    /* Return the number of elements in bag */
    int size();

    /* Store the value in array and return */
    T* elements();
};


/* Bag.cpp */
#include "Bag.h"

template<class T>
Bag<T>::Bag() 
{
    _head = nullptr;
    _size = 0;    
}

template<class T>
Bag<T>::~Bag() {}

/* Insert in the head, no need to traverse, time complexity: O(1) */ 
template<class T>
void Bag<T>::add(T val)
{
    Node<T>* n = new Node<T>(val);
    n->setNext(_head);
    _head = n;
    _size++;
}

/* Return whether the bag is empty or not */
template<class T>
bool Bag<T>::isEmpty() { return _size == 0; }

/* Return the number of elements in bag */
template<class T>
int Bag<T>::size() { return _size; }

/* Store the value in array and return */
template<class T>
T* Bag<T>::elements()
{
    T* ans = new T[_size];
    int count = 0;
    Node<T>* cur = _head;
    while (cur != nullptr)
    {
        ans[count] = cur->getVal(); 
        cur = cur->getNext();
        count++;
    }
    return ans;
};

但是当代码敲完,准备运行测试时,却发现出现undefined reference to的报错提示,告诉我找不到对应的方法。

解决思路

于是我就开动我的小脑筋(指把报错提示复制粘贴到搜索引擎里),开始寻找解决方案。

C++多文件编译配置

首先可能是我们没有编译全部的.cpp文件,只编译了当前执行的main.cpp文件,导致找不到对应的类。

这个解决方法也很简单,我使用的是vscode,只要修改对应的配置文件,即可。我使用的是Code-runner 插件,打开设置,找到他的Executor Map设置,并点击在settings.json中编辑

修改"cpp"栏的$fileName*.cpp(表示当前目录下所有.cpp文件)即可。

如果使用配置文件的话,修改tasks.json文件中args栏的${file}${fileDirname}\\*.cpp也能达到类似的效果。

但是到此,问题还没有得到解决。

模板的缺陷

The problem is that a function template is not a function. It’s a template for creating functions as needed.

So for a template to work, the compiler intuitively needs two pieces of information: The template itself, and the type that should be substituted into it.

When you declare the function template without defining it, you’re only telling the compiler that such a template exists, but not what it looks like. That’s not enough for the compiler to be able to instantiate it, it has to be able to see the full definition as well. The usual solution is to put the entire template in a header that can be included where needed.

template意味着在模板被真正使用之前,编译器并不知道模板套用的是什么类型,应该分配多少空间。而我们指定的类型T在编译之前就相当于是个占位符。而不同的类型对应着不同的类,我们需要让编译器在编译的时候就知道并具体的定义它们,而不只给一个抽象的类型T

我的程序在编译的时候,编译器只知道类型T版本的Bag类,而不知道int版本的Bag类。所以我们要修改代码,让编译器知道我们需要类型版本的Bag类。

而我们只导入了.h文件,.h相当于是一种声明,它方便编译器来找到对应的实现。我们没有真正的定义,编译器无法编译,自然就会出现Undefined reference to报错了。

我们有两种思路去解决:

  • .cpp文件也导入main.cpp中(或者将对应的方法放到.h文件里也是一样的)。这样会让编译器明确模板类的定义。

  • 或者显式申明用到的类型,例如直接写出

    Bag<int>::Bag() 
    {
        _head = nullptr;
        _size = 0;    
    }
    

    但是这样,好像就丧失了泛型的意义所在?

但无论如何,问题得到了解决,这就足够了~

参考链接

C/C++ VScode 多文件编译配置(undefined reference to ‘xxx‘错误) - CSDN

C++ templates, undefined reference - stackoverflow

[后端 C++: 模板类编译过程中出现“undefined reference to”问题](https://www.dazhuanlan.com/ooxx52douban/topics/1547428)