目录
假设有头文件XLog.h
#pragma once
#include <string>
std::string LogTest(const std::string& szLog) { return szLog; }
Main函数包含头文件XLog.h,正常输出
控制台可以正常打印:
#include <iostream>
#include "XLog.h"
int main()
{
std::cout << "main: " << LogTest("test") << std::endl;
}
结果:
main: test
添加PrintLog1.h头文件包含头文件XLog.h,正常输出
#pragma once
#include <iostream>
#include "XLog.h"
class CPrintLog1
{
public:
CPrintLog1() {};
virtual ~CPrintLog1(void) {};
void Test1(const std::string& szLog) { std::cout << "Test1: " << LogTest(szLog) << std::endl;};
};
#include <iostream>
#include "XLog.h"
#include "PrintLog1.h"
int main()
{
std::string szLog = "test";
std::cout << "main: " << LogTest(szLog) << std::endl;
CPrintLog1 objCPrintLog1;
objCPrintLog1.Test1(szLog);
}
结果
main: test
Test1: test
添加PrintLog2.cpp文件包含头文件XLog.h,编译出错
PrintLog2.h
#pragma once
#include <iostream>
#include "XLog.h"
class CPrintLog2
{
public:
CPrintLog2(){};
virtual ~CPrintLog2(void){};
void Test2(const std::string&);
};
PrintLog2.cpp
#include "PrintLog2.h"
#include "XLog.h"
void CPrintLog2::Test2(const std::string& szLog)
{std::cout << "Test2: " << LogTest(szLog) << std::endl;}
main.cpp
#include <iostream>
#include "XLog.h"
#include "PrintLog2.h"
int main()
{
std::string szLog = "test";
std::cout << "main: " << LogTest(szLog) << std::endl;
CPrintLog2 objCPrintLog2;
objCPrintLog2.Test2(szLog);
}
编译出错:
PrintLog2.obj : error LNK2005: “class std::basic_string<char,struct std::char_traits,class std::allocator > __cdecl LogTest(class std::basic_string<char,struct std::char_traits,class std::allocator > const &)” (?LogTest@@YA?AV?
b
a
s
i
c
s
t
r
i
n
g
@
D
U
?
basic_string@DU?
basicstring@DU?char_traits@D@std@@V?$allocator@D@2@@std@@AEBV12@@Z) 已经在 ConsoleApplication.obj 中定义
1>G:\VS2019\ConsoleApplication\x64\Release\ConsoleApplication.exe : fatal error LNK1169: 找到一个或多个多重定义的符号
函数放在头文件中时被多次包含,编译出错原因
在链接时编译器不清楚要链接哪一个同名函数,因为每个.cpp的编译都是独立的,对于main.cpp和PrintLog2.cpp每个cpp来说,都包含了XLog的声明和实现( 找到了两个同名的函数,LogTest重定义)。
在头文件中使用了#pragma once或是#ifdef __xxx /#define __xxx/#endif也不能解决这种问题。
解决方法
方法一: 添加inline关键字:
inline函数在编译阶段展开,普通函数在运行阶段执行,所以编译时
函数的内容代码被直接解释到调用处,链接时相当于不存在这个函数,也就不存函数重定义的情况。
参考博客:C++之inline函数(内联函数)详解
#pragma once
#include <string>
inline std::string LogTest(const std::string& szLog){ return szLog;}
注意:
(1)内联函数只适用于功能简单的小函数(几行), 函数体中包含循环、switch等复杂语句的或语句比较多的函数,即便添加了inline,编译器也往往会忽略并将其作为普通函数处理。递归函数是不能被用来做内联函数的。
(2)函数声明和定义处都需要添加 inline 关键字,若只在声明处添加 inline ,编译器会忽略 inline 关键字,编译器视为普通函数。一般声明和实现都走头文件完成。
方法二: 添加static关键字,编译器链接时,只能连接到一份此函数,不存在重定义
参考博客:static修饰的函数作用与意义
#pragma once
#include <string>
static std::string LogTest(const std::string& szLog) { return szLog;}
方法三:XLog.h 中使用模板 template
参考博客: 关于C++编译链接和模板函数
#pragma once
#include <string>
#include <sstream>
template <typename T>
std::string LogTest(T t)
{
std::stringstream ssLog;
ssLog << t;
return ssLog.str();
}
C++11及以上版本可实现输入不定个数参数
#pragma once
#include <string>
#include <sstream>
template <typename T>
void Print(std::stringstream& ss, T&& arg)
{
ss << "[" << arg << "]";
}
template<typename ...Args>
std::string LogTest(Args&&... args)
{
std::stringstream ssLog;
int a[] = { (Print(ssLog,std::forward<Args>(args)), 0)... };
return ssLog.str();
}