专栏首页游戏开发司机你担心大家会滥用的全局变量,大家(包括你自己)一定会滥用

你担心大家会滥用的全局变量,大家(包括你自己)一定会滥用

前言

不要使用全局变量的道理大家都懂,基本上在大家学习编程过程中很早就会被教育到,但是有时候我们也会禁不住诱惑用到一些似非实是的全局变量,只不过这些全局变量会穿上马甲,让你不会一下看穿它的巨大危害,滥用全局变量会引申带来其它更为严重的结构性系统问题。

你担心大家会滥用的代码,大家(包括你自己)一定会滥用。

                                              -- 鲁迅。

滥用全局变量的危害

1. 滥用全局变量会造成不必要的常量频繁使用,特别当这个常量没有用宏定义“正名”时,代码阅读起来将万分吃力。 2. 会导致软件分层的不合理,全局变量相当于一条快捷通道,这在软件系统的构建初期的确效率很高,功能调试进度一日千里,但到了后期往往bug一堆,处处“补丁”,雷区遍布。

3. 由于软件的分层不合理,到了后期维护,哪怕仅是增加修改删除小功能,往往要从上到下掘地三尺地修改,涉及大多数模块,而原有的代码注释却忘了更新修改,这个时候,交给后来维护者的系统会越来越像一个“泥潭”,注释的唯一作用只是使泥潭上方再加一些迷烟瘴气。 4. 全局变量大量使用,少不了有些变量流连忘返于中断与主回圈程序之间。这个时候如果处理不当,系统的bug就是随机出现的,无规律的,这时候初步显示出病入膏肓的特征来了,没有大牛来力挽狂澜,注定慢性死亡。 无需多言,您已经成功得到一个畸形的系统,它处于一个神秘的稳定状态!

本篇文章我不再赘述全局变量,局部变量等基础知识,感兴趣的可以查看历史文章。

滥用全局变量示例

读到这里,对于新手们来说可能还没有意识到全局变量的危险性,这里我先拿出来一段不太复杂的一段代码:

#include <iostream>
#include <string>
#include <map>
using namespace std;

class Monster
{
private:
    int npc_id;
public:
        Monster(int npc_id):npc_id(npc_id){
            cout << "call Monster::Init" << npc_id <<endl;
        }
        int NpcID(){
            return npc_id;
        }
            virtual void Stand(){
            cout << "call Monster::Stand" <<endl;    
        }
        virtual void Walk(){
            cout << "call Monster::Walk" <<endl;
        }
        virtual int Stop(){
            cout << "call Monster::Stop" <<endl;
        }
        ~Monster(){
            cout << "call ~Monster" <<endl;
        }
};

class NPCFactory
{
public:
        static NPCFactory& Instance(){
            cout << "call static NPCFactory" <<endl;
            static NPCFactory instance;
            return instance;
        }
        ~NPCFactory(){
            cout << "call ~NPCFactory" <<endl;
        }
        Monster* Create(int npc_id){
            map<int,Monster*>::iterator itr = monsters_.find(npc_id);
            if (itr != monsters_.end()) {
                return itr->second;
            }
            Monster* monster = new Monster(npc_id);
            monsters_.insert({ {npc_id, monster} });
            return monster;
        }
        bool Release(Monster* monster){
            if (monster == nullptr){
                return false;
            }
            int npc_id = monster->NpcID();

            map<int,Monster*>::iterator itr = monsters_.find(npc_id);
            if (itr == monsters_.end()) {
                 return false;
            }
            delete monster;
            monsters_.erase(npc_id);
            return true;
        }
private:
        map<int, Monster*> monsters_;
};

#define testobj MonsterMgr::Instance()
class MonsterMgr
{
public:
        virtual ~MonsterMgr(){
            cout << "call ~MonsterMgr" <<endl;
            for (auto d : monsters_){
                 NPCFactory::Instance().Release(d.second);
            }
        }

        Monster* Create(const uint32_t& npc_id)
{
            Monster* monster = NPCFactory::Instance().Create(npc_id);
            monsters_.insert(std::make_pair(npc_id, monster));
            return monster;
        }
        static MonsterMgr& Instance(){
            cout << "call static MonsterMgr" <<endl;
            static MonsterMgr npc_mgr;
            return npc_mgr;
        }
private:
        std::map<uint32_t, Monster*> monsters_;
};

int main(){
    testobj.Create(1000);
    return 0;
}

看了几遍没看出来哪里有问题?不妨运行看下

没错,程序崩溃了,提示double free了?为什么?

归根到底还是全局变量使用的泛滥导致的。

首先我们来分析下:

testobj是一个静态全局变量,在调用Create方法里,通过NPCFactory的静态全局变量工厂方法创建了Monster实例,因此在出了main函数之后,先释放了静态全局变量NPCFactory的成员,由于工厂方法没有正常释放monsters_的成员,造成了内存泄露,实际上这个时候的monsters_里的指针已经变成了野指针,紧接着释放静态全局变量testobj,进入析构MonsterMgr,逐个释放Monster指针,注意这里:NPCFactory::Instance().Release(m_monster); 实际上m_monster已经在NPCFactory工厂释放掉了,成为了野指针,所以在调用NPCFactory::Instance().Release(m_monster);的时候就出现了释放野指针崩溃。

这次相信你可能会比较谨慎的使用全局变量了,尤其是穿梭在多个功能里,跟玩“鳞波微步”一样的潇洒,岂不知已经给后面埋下了地雷。

此时的系统因为离不开你了,所有“雷区”只有你了然于心。当出现紧急的bug时,只有你能够搞定。老板不但不能辞退你,还要给你加薪。 但凡招聘来维护这个系统的,除了改出更多的bug外,基本上一个月内就走人,到了外面还宣扬这个公司的软件质量有够差够烂。 随着产品的后续升级,几个月没有接触这个系统的你会发现,很多雷区你本人也忘记了,于是每次的产品升级维护周期越来越长,因为修改一个功能会冒出很多bug,而按下一个bug,会弹出其他更多的bug。在这期间,又会产生更多的全局变量。终于有一天你告诉老板,不行啦不行啦,资源不够了,内存太小了,升级升级,某个库用的有问题,要换掉!!! 那么有什么对策?

1. 能不用全局变量尽量不用,我想除了系统状态和控制参数、通信处理和一些需要效率的模块,其他的基本可以靠合理的软件分层和编程技巧来解决。 2. 如果不可避免需要用到,那能藏多深就藏多深。

1)如果只有某.c文件用,就static到该文件中,顺便把结构体定义也收进来; 2)如果只有一个函数用,那就static到函数里面去; 3)如果非要开放出去让人读取,那就用函数return出去,这样就是只读属性了; 4)如果非要遭人蹂躏赋值,好吧,我开放函数接口让你传参赋值;

5)实在非要extern我,我还可以严格控制包含我.h档的对象,而不是放到公共的includes.h中被人围观,丢人现眼。

继续带着刚才的问题,那么如何解决崩溃问题呢?

两种方式:

1.如果你的代码逻辑本身够复杂,大动干戈,想最小代价来修复问题,你可以在NpcFactory析构之前,主动释放掉NpcMgr里的一堆指针,新增一个NpcMgr的Clear方法,用于在main函数结束之前主动调用来释放资源。

class MonsterMgr
{
public:
        virtual ~MonsterMgr(){
            cout << "call ~MonsterMgr" <<endl;
            Clear();
        }
        virtual void Clear(){
             for (auto d : monsters_){
                 NPCFactory::Instance().Release(d.second);
            }
        }

        Monster* Create(const uint32_t& npc_id)
{
            Monster* monster = NPCFactory::Instance().Create(npc_id);
            monsters_.insert(std::make_pair(npc_id, monster));
            return monster;
        }
        static MonsterMgr& Instance(){
            cout << "call static MonsterMgr" <<endl;
            static MonsterMgr npc_mgr;
            return npc_mgr;
        }
private:
        std::map<uint32_t, Monster*> monsters_;
};

int main(){
    testobj.Create(1000);
    testobj.Clear();
    return 0;
}

但是我不建议这样做,因为你将给其他人带来麻烦,必须要主动显式调用Clear方法,如果某一天,有人改动了一些Clear的逻辑,你可能仍然面临加班排查问题的境地。

2.就是重构设计,尽可能避免全局变量引发的问题。

其实你主要要解决的是如何避免在MonsterMgr释放所有的Monster指针之前已经将NPCFactory提前释放导致monster野指针问题(这里我不讨论NPCFactory工厂类的释放Release存在的问题),因此你可以考虑下让工厂类作为MonsterMgr的类静态成员,通过静态类成员来管理monster,这样,你可以保证在析构MonsterMgr释放所有monster指针之前,工厂类管理的所有monster指针是有效的。

#include <iostream>
#include <string>
#include <map>
using namespace std;

class Monster
{
private:
    int npc_id;
public:
        Monster(int npc_id):npc_id(npc_id){
            cout << "call Monster::Init" << npc_id <<endl;
        }
        int NpcID(){
            return npc_id;
        }
            virtual void Stand(){
            cout << "call Monster::Stand" <<endl;    
        }
        virtual void Walk(){
            cout << "call Monster::Walk" <<endl;
        }
        virtual int Stop(){
            cout << "call Monster::Stop" <<endl;
        }
        ~Monster(){
            cout << "call ~Monster" <<endl;
        }
};

class NPCFactory
{
public:
        static NPCFactory& Instance(){
            cout << "call static NPCFactory" <<endl;
            static NPCFactory instance;
            return instance;
        }
        ~NPCFactory(){
            cout << "call ~NPCFactory" <<endl;
        }
        Monster* Create(int npc_id){
            map<int,Monster*>::iterator itr = monsters_.find(npc_id);
            if (itr != monsters_.end()) {
                return itr->second;
            }
            Monster* monster = new Monster(npc_id);
            monsters_.insert({ {npc_id, monster} });
            return monster;
        }
        bool Release(Monster* monster){
            if (monster == nullptr){
                return false;
            }
            int npc_id = monster->NpcID();

            map<int,Monster*>::iterator itr = monsters_.find(npc_id);
            if (itr == monsters_.end()) {
                 return false;
            }
            delete monster;
            monsters_.erase(npc_id);
            return true;
        }
private:
        map<int, Monster*> monsters_;
};

#define testobj MonsterMgr::Instance()
class MonsterMgr
{
public:
        virtual ~MonsterMgr(){
            cout << "call ~MonsterMgr" <<endl;
            for (auto d : monsters_){
                 npc_factory_.Release(d.second);
            }
        }

        Monster* Create(const uint32_t& npc_id)
{
            Monster* monster = npc_factory_.Create(npc_id);
            monsters_.insert(std::make_pair(npc_id, monster));
            return monster;
        }
        static MonsterMgr& Instance(){
            cout << "call static MonsterMgr" <<endl;
            static MonsterMgr npc_mgr;
            return npc_mgr;
        }
private:
        std::map<uint32_t, Monster*> monsters_;
        static NPCFactory&           npc_factory_;       
};

NPCFactory& MonsterMgr::npc_factory_ = NPCFactory::Instance();

int main(){
    testobj.Create(1000);
    return 0;
}

再考虑个问题,如果后边增加一个需求模块,要求这个模块也可以管理怪物类Monster,暂且叫NPCMgr,功能和原来的MonsterMgr类似,此时你觉得应该如何设计此NPCMgr类,留下来思考时间给大家。

文章分享自微信公众号:
游戏开发司机

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:Oo桂圆肉oO
原始发表时间:2022-03-24
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 一步步教你编写不可维护的 PHP 代码

    随着失业率越来越高,很多人意识到保全自己的工作是多么的重要。那么,什么是保住自己工作,并让自己无可替代的好方法呢?一个很简单的事实是只要你的代码没有人能够维护,...

    猿哥
  • 【Goldberg回应LeCun】DL社群缺乏学习,夸大研究成果

    【新智元导读】Yann LeCun 对于 Yoav Goldberg 的驳斥得到了 Goldberg 第一时间的回应。他表示自己并不反对在语言任务上使用深度学习...

    新智元
  • GitHub 发了大福利后,羊毛党正在路上

    内容提要:GitHub 从今天起,面向全体个人和团队,免费提供不限制协作人数的私有仓库,所有核心功能也统统免费。不过,全面免费政策背后,也带来一些隐忧。

    HyperAI超神经
  • C语言中,全局变量滥用的后果竟如此严重?

    说起全局变量,就不得不提到“全局变量,局部变量,静态全局变量,静态局部变量”,这些都是编程语言中的基本概念。变量分为局部与全局,局部变量又可称之为内部变量。由某...

    C语言与CPP编程
  • 十年之后再看“面向对象”

    精讲java
  • 十年之后再看“面向对象”

    一起帮里有人问“面向对象”的问题。但我创建“一起帮”的目的是帮人解决“具体的”“实务性的”问题,“面向对象”太过于抽象,所以没批准发布。后来在QQ群里讨论,看他...

    用户1257393
  • OpenAI发布首个商业产品,集成GPT-3的API,已有十几家公司买单

    据OpenAI官方博客报道,其于昨日发布了一个文本生成的API,通过这个API用户可以尝试任何形式的文本生成类的英语任务,包括但不限语义搜索,摘要,情感分析,内...

    AI科技评论
  • 拒绝不公平的师生关系,MIT霸气护学生:你换导师,我替你买单

    这应该很难实现。可能你会遇到很糟糕的导师导致研究之路变得很艰辛,又或者导师学术水平有限不能提供有力的帮助,又或是你的导师只是把你当做项目苦力。对于很多遭遇困难的...

    机器之心
  • JavaScript闭包实例讲解

    闭包是JavaScript语言中的难点,很多刚入行的(包括我在内)一时对他很难理解,于是在网上各种搜罗有关闭包的学习资料,但是无数的文章介绍闭包,但都是了解一个...

    ZEHAN
  • 你滥用log了吗

    代码Review的时候,遇到过一些log滥用的情况,今天聊一聊滥用(过渡使用)日志。

    大彬
  • JS闭包总结

    在JavaScript中全局变量是个不小的毒瘤,全局变量有时是很方便,但是很多项目滥用全局变量成灾,维护起来非常困难。所以这里的作用就是防止全局变量污染,例子如...

    用户1428723
  • 知识点归纳笔记:JavaScript编码规范你都了解多少?

    驼峰式命名法大家应该都比较熟悉了:驼峰式命名法又被称为骆驼命名法,它是由小(大)写字母开始,后续每个单词首字母都大写。其中首字母大写的命名称为大驼峰命名法(Pa...

    用户1272076
  • OpenAI担心自家AI太强大不公开代码,网友嘲讽:改名CloseAI算了

    昨天,OpenAI在官博宣布,他们构建了一个强大的NLP模型,但正因为这个模型过于强大,能够生成以假乱真的句子,为了避免其遭到滥用,他们决定不公开模型的具体结构...

    新智元
  • “一键脱衣”,又卷土重来了!

    不知道大家还记得 “一键脱衣 AI” DeepNude 和 “一键换脸”的 Deepfake吗?

    开发者技术前线
  • 优秀!高级Java都这样优雅处理空值

    在笔者几年的开发经验中,经常看到项目中存在到处空值判断的情况,这些判断,会让人觉得摸不这头绪,它的出现很有可能和当前的业务逻辑并没有关系。但它会让你很头疼。

    JAVA葵花宝典
  • 使用Optioanl优雅的处理空值

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    chenchenchen
  • 睫毛长度精准复刻!扫描面部数据,用特殊技术和3D打印制作人脸,“画皮”直呼内行

    喜欢科幻电影的朋友们对于换脸已经是见怪不怪了,比如在2014年大热的《机械姬》中,出现了下面这一幕。

    大数据文摘
  • 心理医生妈妈是怎样育儿的?

    用户1756920
  • 你知道吗:facebook员工无需密码,就能访问你的账号!

    毫无疑问,在不久的将来,Facebook和其他大型科技公司:包括谷歌,苹果和雅虎正试图通过采用终端到终端通信加密解决方案,来确保他们的数据不会被执法、间谍机构窃...

    安恒信息

扫码关注腾讯云开发者

领取腾讯云代金券