ATEngin开发记录_3_解决inline内联函导致的 LNK2001错误

该系列只做记录 不做教程 所以文章简洁直接 会列出碰到的问题和解决方案 只适合C++萌新


问题

我在项目中定义了两个内联函数GetCoreLogger()和GetClientLogger(),将项目打包成dll没有报错,但是被另一个项目调用的时候总是报错:
1>SandBoxApp.obj : error LNK2001: 无法解析的外部符号 "private: static class std::shared_ptr<class spdlog::logger> ATEngine::Log::s_CoreLogger" (?s_CoreLogger@Log@ATEngine@@0V?$shared_ptr@Vlogger@spdlog@@@std@@A) 1>SandBoxApp.obj : error LNK2001: 无法解析的外部符号 "private: static class std::shared_ptr<class spdlog::logger> ATEngine::Log::s_ClientLogger" (?s_ClientLogger@Log@ATEngine@@0V?$shared_ptr@Vlogger@spdlog@@@std@@A) 1>F:\001_UnityProjects4\ATEngine\x64\Debug\SandBox.exe : fatal error LNK1120: 2 个无法解析的外部命令

在这里插入图片描述

为什么使用内联函数?
内联函数最初的目的:代替部分 #define 宏定义;
使用内联函数替代普通函数的目的:提高程序的运行效率;

看大佬博客:【C++】C++中内联函数详解(搞清内联的本质及用法)


#pragma once
#include <memory>
#include "Core.h"
#include "spdlog/spdlog.h"

namespace ATEngine
{
    class HAZEL_API Log
    {
    public:
        static void Init();

        inline static std::shared_ptr<spdlog::logger>& GetCoreLogger()
        {
            return s_CoreLogger;
        }

        inline static std::shared_ptr<spdlog::logger>& GetClientLogger()
        {
            return s_ClientLogger;
        }

    private:
        static std::shared_ptr<spdlog::logger> s_CoreLogger;
        static std::shared_ptr<spdlog::logger> s_ClientLogger;
    };
}

解决方案

先不用inline,而是将声明和实现分开,就能解决!

#pragma once
#include <memory>
#include "Core.h"
#include "spdlog/spdlog.h"

namespace ATEngine
{
    class HAZEL_API Log
    {
    public:
        static void Init();

        static std::shared_ptr<spdlog::logger>& GetCoreLogger();  
        static std::shared_ptr<spdlog::logger>& GetClientLogger(); 

    private:
        static std::shared_ptr<spdlog::logger> s_CoreLogger;
        static std::shared_ptr<spdlog::logger> s_ClientLogger;
    };
}

#include "Log.h"
#include "spdlog/sinks/stdout_color_sinks.h"

namespace ATEngine
{
    std::shared_ptr<spdlog::logger> Log::s_CoreLogger;
    std::shared_ptr<spdlog::logger> Log::s_ClientLogger;

    void Log::Init()
    {
        spdlog::set_pattern("%^[%T] %n: %v%$");
        s_CoreLogger = spdlog::stdout_color_mt("HAZEL");
        s_CoreLogger->set_level(spdlog::level::trace);

        s_ClientLogger = spdlog::stdout_color_mt("APP");
        s_ClientLogger->set_level(spdlog::level::trace);
    }

    std::shared_ptr<spdlog::logger>& Log::GetCoreLogger()
    {
        return s_CoreLogger;
    }

    std::shared_ptr<spdlog::logger>& Log::GetClientLogger()
    {
        return s_ClientLogger;
    }
}

原因分析(AI回答)

1.静态成员函数和静态成员变量
静态成员变量和静态成员函数属于类的 类级别 资源,而不是某个实例。静态成员变量和函数的声明与普通的实例成员不同,主要有以下几个特点:

静态成员变量:在类的所有实例之间共享,并且在类加载时只会有一份副本。静态成员变量必须在 .cpp 文件中定义,即使在头文件中声明了它们。

静态成员函数:静态成员函数只能访问静态成员变量,且它们没有 this 指针。与静态成员变量一样,静态成员函数也通常会在头文件中声明,但必须在 .cpp 文件中提供定义,除非它们是 inline 函数。

2.inline 和 static 关键字的行为
inline:当你声明一个函数为 inline 时,编译器会尝试将该函数的代码直接嵌入到每个调用它的地方。这意味着该函数体会出现在多个翻译单元中。这可以提高性能,但也会带来重复定义的问题,特别是在多个翻译单元中包含了头文件的情况下。
static:当你在函数前面加上 static 时,它的作用是将该函数的作用域限制在当前文件中。也就是说,静态函数无法在其他文件中访问。因此,静态函数不能在其他翻译单元中发生链接冲突。

3.头文件与源文件中的区别
头文件:通常用于声明类、函数、常量、宏等。头文件中的函数声明,特别是静态成员函数的声明,仅告诉编译器该函数的接口(即函数名和参数),但并不包含其实现(即函数体)。

源文件:源文件是包含函数定义的地方,函数体的实际代码通常在 .cpp 文件中。只有在这里,静态成员函数才能被实际编译和链接。

4.为什么 .cpp 中定义静态成员函数能够解决问题
静态成员函数的声明需要在头文件中出现,但它的定义(即函数体)必须在一个单独的 .cpp 文件中给出。只有在 .cpp 文件中提供了静态成员函数的定义后,链接器才能找到它的实际实现,避免链接错误。

5.总结为什么 inline static 会导致问题
当你把 inline 和 static 都加到函数声明中时,编译器会试图把函数的实现插入到每个翻译单元里,导致每个翻译单元中都包含函数的实现。这通常在 inline 函数中是允许的,但是如果有多个翻译单元在不同的地方包含了这个头文件,可能会导致 重复定义 问题。

inline 强制要求函数体出现在头文件中,而 static 让每个翻译单元都能独立地访问该函数。这会导致函数体的定义在多个翻译单元中重复出现,导致链接器报错。

通过将函数的定义移到 .cpp 文件中,你确保了该函数只有一个定义(避免重复),同时保留了头文件中的声明,从而解决了链接问题。

因此,头文件中声明静态成员函数,.cpp 文件中定义它们 是一个合适的做法。这种分离声明和定义的方法不仅能避免链接错误,还能使代码更加模块化和清晰。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值