What I am Doing

My Major Is


Unity

Unity3D is a multi-platform integrated game development tool developed by Unity Technologies, which makes it easy for players to create interactive content such as three-dimensional video games, building visualization, real-time three-dimensional animation and so on. It is a fully integrated professional game engine.

Shader

Shader is a relatively short program fragment used to tell graphics software how to calculate and output images. Shader can be divided into two main categories: Vertex Shader (Vertex Shader) and Fragment Shader (Fragment Shader)

Game

Great Nintendos fans, favor in ALL kind of game. Want to Work in Seasun. Favourites is MMORPG, ARPG games like Zeldas.

My Latest

News And Blog


C++——简单对象池的实现

为什么写简单对象池?因为我暂时想不到有什么值得完善的,如果有我还是非常乐意去搞搞看。

那么话不多说直接开搞。

什么是对象池?就是一个池子,里面有很多对象,你可以借出,借出后用完再还回去。为什么要有对象池这么东西显而易见,我们省去了对象的创建,岂不美哉?

那么我们的对象池既然要借出,就必须要有对象在里面,就像你去借共享单车,没有单车停在那里你借个啥?

那我们先写初始化,可以想象共享单车公司开始放单车了:

    ObjectPool(int Size):_MaxSize(Size){
        if(_MaxSize){
            for(int i = 0;i<_MaxSize;i++)
            {
                Object_List.push(new Object(i));
            }
        }
        else{
            printf("Init Error!\n");
        }
    }

可以看出来,只要_MaxSize值不为0,那就能成功初始化,顺便说一下我这里是new一个放一个,是写着方便,网上也有另外一种初始化方法。

    Object* pT = reinterpret_cast<Object*》(malloc(_MaxSize*sizeof(Object)));
    for(int i = 0;i<_MaxSize;i++)
    {
        (pT+i)->ID = i;
        Object_List.push(pT+i);
    }

这个大同小异,就是实现方法有点不同,是先申请那么多个内存,然后一段一段放回进去,这样申请出来的指针内存位置是连续的,我那种就不一定。另外就是一个很基础的东西,new对应delect,malloc对应free。

然后我们再来说借出:

    Object* Get_Object(){
        Object* object = Object_List.front();
        if(Object_List.empty()){
            printf("No Enough Object\n");
            object = new Object(999);
            return object;
        }
        else{
            object = Object_List.front();
            Object_List.pop();
            return object;
        }
    }

代码显而易见,只不过不要学我在处理队空时所申请内存的方法,怎么改都行,不难。

最后是放回,也非常简单:

void Give_Back(Object* object){
    if(Object_List.size()==_MaxSize){
        printf("Delect %d!\n",object->ID);
        delete object;
    } else {
        printf("Push %d Back!\n",object->ID);

        Object_List.push(object);
    }
}

然后我们来测试一下:

int main(){
ObjectPool newPool(3);
Object* item_1 = newPool.Get_Object();
Object* item_2 = newPool.Get_Object();
Object* item_3 = newPool.Get_Object();

PrintAddress(item_1);
PrintAddress(item_2);
PrintAddress(item_3);

newPool.Give_Back(item_1);
Object* item_4 = newPool.Get_Object();
PrintAddress(item_4);
}

结果是:

This is an Object ID = 0 and Address is 0x7ff8b4c02590 
This is an Object ID = 1 and Address is 0x7ff8b4c02594 
This is an Object ID = 2 and Address is 0x7ff8b4c02598 
Push 0 Back!
This is an Object ID = 0 and Address is 0x7ff8b4c02590 

完全符合我们的预期,那最后来做个有意思的模拟吧,从这个模拟顺便说说线程池的缺点,前面叨叨了那么多共享单车,我们假设每个线程池是一个地方,每个物体是一个共享单车,我们现在来模拟一下一段时间内这些车被人骑走还回的情况。

#include<cstdio>
#include <queue>
class Bike{
public:
    int _ID;
    int _NowWhere;
    Bike(int ID,int NowWhere){
        this->_ID = ID;
        this->_NowWhere = NowWhere;
    }
    void MoveTo(int NextWhere){
        _NowWhere = NextWhere;
        printf("This Bike is move to %d\n", NextWhere);
    }
};

class Place{
    int _MaxSize;
public:

    int _PlaceID;
    std::queue<Bike*> Object_List;
    Place(int Size,int PlaceID):_MaxSize(Size){
        _PlaceID = PlaceID;
        if(_MaxSize){
            Bike* pT = reinterpret_cast<Bike*>(malloc(_MaxSize*sizeof(Bike)));
            for(int i = 0;i<_MaxSize - 5;i++)
            {
                (pT+i)->_ID = i;
                (pT+i)->_NowWhere = _PlaceID;
                Object_List.push(pT+i);
            }
        }
        else{
            printf("Init Error!\n");
        }
    }

     Bike* Borrow(){
        Bike* object = Object_List.front();
        if(Object_List.empty()){
            printf("Sorry , No Bike To Borrow !\n");
            return object;
        }
        else{
            object = Object_List.front();
            Object_List.pop();
            return object;
        }
    }
    void Give_Back(Bike* object){
        if(Object_List.size()==_MaxSize){
            printf("Error!\n");
        } else {
            printf("%d is Give Back at %d!\n",object->_ID,object->_NowWhere);
            Object_List.push(object);


        }
    }
    int GetMaxSize(){
        return _MaxSize;
    }
};
void GetSum(Place& a,Place& b,Place& c)
{
    printf("***************\nThere are %lu bike at TianHeCity!\nThere are %lu bike at ZhujiangCity!\nThere are %lu bike at WushanStation!\n***************\n",a.Object_List.size(),b.Object_List.size(),c.Object_List.size());
}
void RideAway(Bike* Bike_Item,Place& NextPlace){
    Bike_Item->MoveTo(NextPlace._PlaceID);
    NextPlace.Give_Back(Bike_Item);
}
int main(){
    Place TianHeCity(10,1);
    Place ZhuJiangCity(10,2);
    Place WuShanStation(10,3);
    GetSum(TianHeCity,ZhuJiangCity,WuShanStation);

    if(Bike* Bike_1 = TianHeCity.Borrow())
    {
        RideAway(Bike_1,ZhuJiangCity);
    }

    if(Bike* Bike_2 = TianHeCity.Borrow())
    {
        RideAway(Bike_2,ZhuJiangCity);
    }

    if(Bike* Bike_3 = TianHeCity.Borrow())
    {
        RideAway(Bike_3,ZhuJiangCity);
    }

    if(Bike* Bike_4 = TianHeCity.Borrow())
    {
        RideAway(Bike_4,ZhuJiangCity);
    }

    if(Bike* Bike_5 = TianHeCity.Borrow())
    {
        RideAway(Bike_5,ZhuJiangCity);
    }

    if(Bike* Bike_6 = TianHeCity.Borrow())
    {
        RideAway(Bike_6,ZhuJiangCity);
    }

    GetSum(TianHeCity,ZhuJiangCity,WuShanStation);
}

下面main函数写的有点弱智,但是方便阅读。可以看到大家都喜欢从天河城骑车去珠江新城,所以天河城被骑到没车了,而珠江新城一堆车,幸好珠江新城地方比较大能停,如果不能停就糟糕了。

一般的对象池如果满了,会选择直接删除,但是如果你是写共享单车对象池的人,肯定不能把车直接扔了,老板不杀了你才怪。这个时候我们有别的解决方法,这里提供一个思路:可以给Give_Back添加返回值,如果没有归还成功就返回一个有位置的对象池,实现这个功能需要借助一个外部函数,但是不难,所以不写了,这里着重说一下对象池最大的一个问题。

想象这里突然来了一个缺德的人,拿了车不还,我们在统计车数的时候就会发现如下信息:

***************
There are 0 bike at TianHeCity!
There are 9 bike at ZhujiangCity!
There are 5 bike at WushanStation!
***************

惨了少了一辆,现实中我们损失的是资金,但是在程序中,我们就等于失去了一块内存,你不还还去借下一个,那就会导致内存泄露,这个是在事发后没有办法解决的问题,所以要么就完善对象池让他自动回收(利用析构函数),要么就记得要还。

OK,对象池就写这么多。


C++——关于右值引用及其拓展

这几天在复习以前的内容后发现C++有个右值引用之前一直没有了解过,它的好处是可以让运行速度变快。

怎么区分左值和右值?我的理解是带变量名,可以取地址的为左值。纯数值,不能取地址的为纯右值,在操作后就会消失的为将亡值,纯右值和将亡值统称为右值

传统引用也被称为左值引用,是绑定对象的操作,对左值进行引用,它的特点如下:

  • 必须被初始化并且无法解绑
  • 绑定后等于给原本对象取别名,所有的操作都会指向被绑对象
  • 左值引用不能指向临时变量,除了const &a = 1这种形式,因为此时是给1新开了一个临时的左值,这种操作方法称为常量左值引用

右值引用是C11引入的,是对右值进行的引用,特点如下:

  • 只能绑定在将被销毁的变量上
  • 可以理解为值的传递,原本的值会在传递后被删除
  • 右值引用不能指向一个左值,但是可以用&&a = move(b),即std::move()函数让左值传递右值,但是要尽量保证被传递的值不要再被使用了,这个函数的作用是能将一个左值转换成一个右值

那么为什么要有右值引用,原本的赋值不能满足吗?先说一下移动语义:

移动语义是指只进行移动操作的转换。传统的赋值是先创造要传递值的副本,然后赋给另一个对象,最后删除原先对象保存的值。移动语义就简单很多,将该值直接传递给另外一个对象。

可以直观的看出,右值引用理论上要比赋值快很多,那么试验一下:

#include <iostream>
#include <thread>


void print_1(int i)
{
    printf("这是普通的赋值函数,i为%d,",i);
}
void print_2(int&& i)
{
    printf("这是右值引用的函数,i为%d",i);
}

int main()
{
    double Start = clock();
    print_1(5);
    double End_1 = clock();
    printf("耗时%lf ms\n",((End_1 - Start)/CLOCKS_PER_SEC)*1000);
    print_2(5);
    double End_2 = clock();
    printf("耗时%lf ms\n",((End_2 - End_1)/CLOCKS_PER_SEC)*1000);
}

程序结果为:

这是普通的赋值函数,i为5,耗时0.019000ms
这是右值引用的函数,i为5耗时0.013000ms

可以发现,右值引用要比赋值函数快太多了,函数越复杂,效果越明显。

在说完美转发之前不能不说的是万能引用,万能引用即可以接受左值也可以接受右值的一种引用方法,下面实例说明一下:

#include <iostream>
#include <thread>


template <typename T>
void JustOutput(T&& Value)
{
    std::cout<<Value<<std::endl;
}
int main()
{
    int a = 5;
    double Start = clock();
    JustOutput(a);
    double End_1 = clock();
    printf("耗时%lfms\n",((End_1 - Start)/CLOCKS_PER_SEC)*1000);
    JustOutput(5);
    double End_2 = clock();
    printf("耗时%lfms\n",((End_2 - End_1)/CLOCKS_PER_SEC)*1000);
}

我们很高兴的发现两种传值都能够被接受,并且右值传递还是更快一点。不过仔细一看,这个万能引用为什么和右值引用长的这么像?怎么区分?

template <typename T> void JustOutput(T&& Value){}
void JustOutput(int&& Value){}

比较一下可以发现只有在出现类型判断的时候才会成为万能引用,也就是说,万能引用与模板息息相关。

那么我们是不是可以认为能够通过这个函数来判断这个是左值还是右值能?加两个函数进行说明:

#include <iostream>
#include <thread>

void Judgement(int& Value)
{
    printf("这是个左值\n");
}
void Judgement(int&& Value)
{
    printf("这是个右值\n");
}
template <typename T>
void JustOutput(T&& Value)
{
    Judgement(Value);
}
int main()
{
    int a = 5;
    double Start = clock();
    JustOutput(a);
    double End_1 = clock();
    printf("耗时%lfms\n",((End_1 - Start)/CLOCKS_PER_SEC)*1000);
    JustOutput(5);
    double End_2 = clock();
    printf("耗时%lfms\n",((End_2 - End_1)/CLOCKS_PER_SEC)*1000);
}

可以发现虽然“理论上应该是”右值引用的速度比“理论上本来就是”左值引用的速度要快很多,但是程序还是返回了他们都是左值引用的结果,这个不难理解,这个右值传递到JustOutput里,就有了一个变量名,Value,将Value传递给下一层函数,那么就一定是个左值,如果想要在不改变属性的情况下传递这个值,那么就需要用到完美转发,非常简单:

Judgement(std::forward<T>(Value));

如上,我们只需要改一行代码,就能改变输出结果,我们看看forward的源代码:

inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
_Tp&&
forward(typename remove_reference<_Tp>::type& __t) _NOEXCEPT
{
    return static_cast<_Tp&&>(__t);
}

可以发现这个代码就做了一件事情,强制转换,所以我们需要先了解一下引用折叠,引用折叠不难理解,就是&+任何值等于&,&&加&&等于&&。为什么要说这个,可以看到这个源码传入的_Tp是函数调用时的T的类型,如果是右值引用,return时和&&进行折叠为&&,如果为左值引用就和&&折叠为&,如果为纯右值,则不进行折叠,直接输出为&&。

那么完美转发是不是一定要有万能引用呢?我的理解为是的,因为如果没有万能引用就不会发生类型判断,也就只有重载,没有完美转发的事情了。