编译模板化类定义时出现链接器错误

问题描述 投票:0回答:1

我正在尝试编译一个使用模板化 Bag 类的小程序。其组织如下:

// main.cpp

#include <iostream>
#include <string>

#include "bag.h"

int main() {
    Bag<std::string> bag;

    bag.add("cheese");
    bag.add("milk");
    bag.add("eggs");
    bag.add("butter");
    bag.add("orange juice");

    std::cout << bag.getCount() << std::endl;

    return 0;
};
// bag.h

#ifndef _BAG
#define _BAG

#include <stdexcept>

const size_t _MAX_SIZE = 5;

template<typename T>
class Bag {
    public:
        Bag();
        int getCount() const;
        bool isEmpty() const;
        bool add(const T& item);

    private:
        size_t count;
        T contents[_MAX_SIZE];
};

#include "bag.cpp"

#endif

// bag.cpp

#include "bag.h"

template<typename T>
Bag<T>::Bag() {
    count = 0;
};

template<typename T>
int Bag<T>::getCount() const {
    return count;
};

template<typename T>
bool Bag<T>::isEmpty() const {
    return !getCount();
};

template<typename T>
bool Bag<T>::add(const T& item) {
    bool flag = count < _MAX_SIZE;
    if (flag) {
        contents[count] = item;
        count++;
    }
    return flag;
};

我使用g++版本11.4.0来编译。当我输入命令

g++ main.cpp bag.cpp
时,我会看到错误

bag.cpp:4:1: error: redefinition of ‘Bag<T>::Bag()’
    4 | Bag<T>::Bag() {
      | ^~~~~~

我正在使用 VS Code。

当我从

#include "bag.cpp"
中删除
bag.h
时,出现错误

/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/bin/ld: /tmp/ccZmwYEn.o:main.cpp:(.text+0x1e): undefined reference to `Bag<std::string>::Bag()'

当我从

#include "bag.h"
中删除
bag.cpp
时,我收到错误

bag.cpp:4:1: error: ‘Bag’ does not name a type
    4 | Bag<T>::Bag() {
      | ^~~

我已在标头中包含了实现代码,但我不喜欢这种廉价的解决方法。我有什么遗漏的吗?不确定如何继续。我感谢我能得到的所有帮助。谢谢你。

c++ templates linker g++
1个回答
0
投票

两个主要问题:

  1. 你有一个圆形

    #include
    #include
    表示简单复制并粘贴文件的全部内容来代替该指令。如果存在循环包含,则意味着编译器必须无限地复制和粘贴它们。从
    #include "bag.h"
    中删除
    bag.cpp

  2. 由于

    bag.cpp
    不是一个可以单独编译的完整翻译单元(由于删除了
    Bag
    而缺少
    #include
    类的定义),因此不应将其编译为一个单元。从编译命令中删除
    bag.cpp

最终会困扰你的第三个问题:

  1. 您不能像使用
    _MAX_SIZE
    那样在头文件中声明全局变量(另外,该名称是 保留供编译器使用,请选择其他名称)。对于这个程序来说这很好,因为您只需
    #include
    该标头一次,但对于任何更复杂的事情,您都会违反“单一定义规则”。使用 C++17,您可以通过
    inline
    来解决这一问题 - 它只会为整个程序创建一个全局变量。在此之前,您可以做到
    static
    - 每个翻译单元都会有自己的副本,但假设您只使用该值,那不会有太大问题。

推荐:

  1. 为您的实施文件选择不同的扩展名。
    .cpp
    对于大多数人来说意味着“翻译单元”。您的文件不是翻译单元,它是标题的一部分。
    .tpp
    是一个常见的扩展,用于告诉其他人“这是一个模板实现,它是标头的一部分”。
© www.soinside.com 2019 - 2024. All rights reserved.