function ... 已经定义了函数体和函数模板

function ... has already a body & function template has already been defined

我有这个头文件:

Utility.h:

#pragma once

#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <Windows.h>

using namespace std;

template<std::size_t> struct int_ {};

template <class Tuple, size_t Pos> 
std::ostream& print_tuple(std::ostream& out, const Tuple& t, int_<Pos>);

struct Timer {
public:
    Timer();
    void start();
    double getMilliSec();
private:
    LARGE_INTEGER frequency;        // ticks per second
    LARGE_INTEGER t1, t2;           // ticks
};

//...

#include "Utility.cpp"

以及这个实现文件:

Utility.cpp:

#include "Utility.h"
template <class Tuple, size_t Pos>
std::ostream& print_tuple(std::ostream& out, const Tuple& t, int_<Pos>) {
     ...
}

Timer::Timer() {
    // get ticks per second
    QueryPerformanceFrequency(&frequency);
}

void Timer::start() {
    QueryPerformanceCounter(&t1);
}

double Timer::getMilliSec() {
    QueryPerformanceCounter(&t2);
    return (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
}

其中returns这个错误:

error C2995: 'std::ostream &print_tuple(std::ostream &,const Tuple &,int_<Pos>)': function template has already been defined
error C2084: function 'Timer::Timer(void)' already has a body
...

问题与守卫无关(如 this 问题中所建议),因为我使用 #pragma once

我读过关于 implementing .tpp files 的模板 class 实现,但我发现它是一个糟糕的解决方案,因为 Visual Studio 不会从此文件格式化任何内容。

试图定义 Utility.tpp:(错误的解决方案)

所以我在 Utility.h 中将 #include "Utility.cpp 替换为 #include "Utility.tpp 并定义...

Utility.tpp

Timer::Timer() {
    // get ticks per second
    QueryPerformanceFrequency(&frequency);
}

void Timer::start() {
    QueryPerformanceCounter(&t1);
}

double Timer::getMilliSec() {
    QueryPerformanceCounter(&t2);
    return (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
}

现在返回的错误是:

1>Memoization.obj : error LNK2005: "public: __cdecl Timer::Timer(void)" (??0Timer@@QEAA@XZ) already defined in HelloWorld.obj
...

这是主要的顺便说一句:

HelloWorld.cpp:

#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//#include "Memoization.cpp"
#include<vector>
#include"Utility.h"

using namespace std;

int main()
{
}

试图定义 Utility.tpp:(正确的解决方案,最终)

正如评论中让我注意到的那样,作为一个白痴,我在 .tpp 文件中导入了 Time 方法,显然我应该导入模板函数,所以在 Utility.tpp 包含 print_tupleUtility.cpp 包含 3 个 Timer 函数。

从 Utility.h 中删除 #include "Utility.cpp" 行。 然后再次编译Utility.cpp

如果您要将 cpp 文件包含到头文件中,则需要将该 cpp 文件排除在项目编译之外。如果不这样做,则头文件将具有 cpp 文件所具有的内容,并且当编译器将 cpp 文件编译为对象时,您将拥有两个定义副本。一个在头文件中,因为它有一个 cpp 文件的副本,一个在 cpp 文件中,因为它是实际编译的。这会导致重新定义错误。

一旦您从编译中删除 Utility.cpp,那么包含 Utility.h 的任何其他 cpp 文件都将拥有完整源代码的副本,因为还包含 Utility.cpp。

要从编译中删除 Utility.cpp,请参阅:How to exclude files from Visual Studio compile?


如果您使用 cpp 文件的唯一原因是 tpp 文件不将 C++ 代码格式化为 C++,那么您只需配置 MSVS 以将 tpp 文件视为 C++ 文件。如果您转到工具 -> 选项 -> 文本编辑器 -> 文件扩展名,您可以为格式添加文件扩展名。在扩展框中输入扩展名,select Microsoft Visual C++ 从编辑器下拉,然后单击添加。

现在您可以使用 tpp 文件而不是 cpp 文件,并且您不必记住从构建中排除 cpp 文件。您还可以遵循现代实践。

主要有两个问题:

  • 您已将预期的 header 代码放置在“.cpp”文件中,默认情况下,您的 IDE 会将其视为主要源代码文件(翻译单元)。

  • 您在 header 的全局命名空间中包含非 inline 函数定义(class 成员函数)。当 header 包含在两个或更多翻译单元中时,您将违反单一定义规则 (ODR)。实际上链接器会报错。

解决这个问题:

  • 将文件扩展名从“.cpp”更改为例如“.hpp”或简单的“.h”。它是供 header 文件使用的代码。文件扩展名应该反映这一点,而不是误导。

  • 声明函数 inline,或将定义放在 class 定义中。


在其他新闻中,可能来自标准库的 <chrono> 时钟之一将满足您的目的,因此您不必在 header 中包含 <windows.h> .它确实拖入了无数不合理的宏。包括,默认情况下,小写 minmax 宏,所以这段代码非常糟糕。