抠腚爱揉曼 Coding Iron Man

17Apr/120

关于模板的一些有趣的东西(续)

Posted by Jay

模板类不会展开未引用的函数

一般情况下,下面的代码是不能通过编译的:

int FooFunc();

class FooClass
{
public:
    int FuncA() { return FooFunc(); }
    int FuncB() { return FooFunc(0); }
};

原因很简单,因为在FooClass::FuncB里面使用了未重载过的带一个参数的函数FooFunc。
但是如果换下面的形式:

template<typename>
class FooClass
{
public:
    int FuncA() { return FooFunc(); }
    int FuncB() { return FooFunc(0); }
};

void TestFunc()
{
    FooClass<void/*Could be any template parameter*/> obj;
    obj.FuncA();
}

这样的FooClass类是可以被编译,并且FooClass::FuncA是可以被正确调用的,哪怕他似乎包含了一个看上去就知道是错误的函数FooClass::FuncB。
但是如果换成下面的形式:

void TestFunc()
{
    FooClass<void/*Could be any template parameter*/> obj;
    obj.FuncB();
}

显式调用FooClass::FuncB是会触发编译错误的。因此我们似乎可以得出一种结论:模板类不会展开未引用的函数。
关于用例,我目前只是在实现能支持任意参数数量的模板函数器类里面用到过,暂时还想不到会被广泛使用的例子。

Filed under: 抠腚 No Comments
11Apr/120

伪虚函数

Posted by Jay

什么是伪虚函数

实际上这是我自己生造的一个名字。
简单来说想要达到的目的就是,在调用某个可能有多个版本函数FooFunc,如果函数版本FooFuncA存在则使用FooFuncA,如果函数版本FooFuncB存在则使用FooFuncB。换言之是想能够在编译期判断是否定义过某个函数,如果有则使用之。

如何检查一个函数是否定义?

其实上面说到的目的通过传统的virtual function很容易实现:子类重写某个virtual function并且实例化该子类对象调用即可。不过这种动态的通过虚表查询的方式始终感觉没有静态判断来得优雅,而且往往很多时候我们只是想针对纯粹的独立的函数做这样的处理而不想引入那些略显多余的类层次结构。
首先C++里面没有办法可以直接检查一个函数是否定义,其次如果在代码中写上一个没有定义过的函数,编译器也会因为找不到该符号而报错。如何解决?通过SFINAE来检查。

9Apr/120

虚跟模板可以兼得?!(续)

Posted by Jay

模板不可用

有种项目开发模式叫做模块式开发,简而言之就是将项目功能分隔成一个个独立的模块,然后每个模块提供预先商定好的接口以便各模块之间的通信。往往在这种模式下面,接口都是通过纯虚的方法定义的,所以在这种开发模式下面,模板是不可用的。

(伪)灵活调用接口

模板提供的方便之处之一便在于调用时可以了传递任意类型的数据而不需要分别针对性的实现。但是在模块接口式开发的模式下面,自然不能直接定义模板化接口。目前来说,我能想到的就是手动重载函数的针对各种可接受类型的版本,然后同样他们都需要被虚化。例如:

class FooInterface
{
    virtual void FooFunc(const char& value) = 0;
    virtual void FooFunc(const int& value) = 0;
    virtual void FooFunc(const long& value) = 0;
    virtual void FooFunc(const float& value) = 0;
}

(伪)结合模板与虚函数

其实这也不是一种很优雅的解决方式,简单来说就是通过模板将任意类型都映射到那些手动定义的虚方法上面。例如:

class FooInterface
{
    template<typename ValueType>
    void FooFunc(const ValueType& value)
    {
        FooFunc(ToCompatibleValueType(value));
    }
    virtual void FooFunc(const char& value) = 0;
    virtual void FooFunc(const int& value) = 0;
    virtual void FooFunc(const long& value) = 0;
    virtual void FooFunc(const float& value) = 0;
}

这样一来你就可以将任意类型的参数传递给这个FooFunc而无需修改添加多余的虚方法的声明定义。但是前提是这里面的ToCompatibleValueType函数能够将你提供的这些任意类型的参数给映射成那些已经被手动重载的版本。

意义何在?

当手动重载的FooFunc函数版本有限,而调用的地方又很多,原始参数类型也多种多样的时候,因为FooFunc这个接口本身是虚的不能模板化,我们不得不面对大量各种类型转换的调用。而通过这个多出来的模板化的FooFunc,我们可以在ToCompatibleValueType里面对所有类型做集中的类型转换(当然这里会用到很多的Meta Programming以及模板偏特化等等)。而在我们调用该方法的地方则可以传递任意类型数据给该函数,看上去便会有类似模板函数那样方便的效果。
ToCompatibleValueType就像是一个过滤器一样,尝试将任意类型转换为可兼容那些手动指定的数据类型,当然如果发现有些类型无法转换的时候,触发一个编译期的错误提醒开发人员也是很有作用的。

Filed under: 抠腚 No Comments
7Mar/120

关于模板的一些有趣的东西

Posted by Jay

来自boost,Loki等等,大多数代码需要手动或者自动重复(参考boost pre-processor repetition)若干份来满足不同需要。

让模板“支持”任意数量类型参数

将多种类型融合为一种类型,用于让模板可以接收任意多类型作为参数:
定义一种“空类型”以及“类型链”

struct NullType {};
template<typename _H, typename _T>
struct TypeChain
{
    typedef _H Head;
    typedef _T Tail;
};

然后就可以有:

TypeChain<_T1, NullType>
TypeChain<_T1, TypeChain<_T2, NullType>>
TypeChain<_T1, TypeChain<_T2, TypeChain<_T3, NullType>>>

用这些类型都可以带入其他模板用作类型参数。更多参数版本可以通过repetition自动展开。

解析函数原型中返回值以及所有参数类型

通过模板偏特化实现对函数原型,首先定义最原始的解析模板类:

template<typename>
struct Signature;

然后分别偏特化无参数,有一个参数,有二个参数到有若干个参数的版本例如:

template<typename _R>
struct Signature<_R (*)()>
{
    typedef _R ResultType;
};

template<typename _R, typename _P1>
struct Signature<_R (*)(_P1)>
{
    typedef _R  ResultType;
    typedef _P1 ParamType1;
};

template<typename _R, typename _P1, typename _P2>
struct Signature<_R (*)(_P1, _P2)>
{
    typedef _R ResultType;
    typedef _P1 ParamType1;
    typedef _P2 ParamType2;
};

该模板可以直接接受函数原型作为模板参数,例如:

Signature<int(float, char)>

然后可以进一步获取返回值和参数的类型,例如:

typedef Signature<int(float, char)> TheSignature;
typedef typename TheSignature::ResultType ResultType;  // Type int
typedef typename TheSignature::ParamType1 ParamType1;  // Type float
typedef typename TheSignature::ParamType2 ParamType2;  // Type char

更多参数版本依然可以通过repetition来自动实现。

Tagged as: No Comments
28Dec/113

虚跟模板可以兼得?!

Posted by Jay

由某个案例引发出来...

为什么要虚

在OOP的开发过程中,往往会有大量对象的定义和使用,很多时候我们需要将这些对象持久化的保持在外部文件里面,然后再在需要的时候将其读取进来。因为对象的类型数量都很多,一个一个去实现如何写入和读取就特别冗余且笨重。常见的解决方式便是序(反序)列化,简而言之就是为对象的基类都加上serialize/deserialize的虚方法,然后子类只需要实现如何组织需要写入和读取的数据就可以了。此后我们只要有一个serializable的对象便能对其进行读写了。

为什么要模板

但是关于具体的序列化的时候,便会遇到一些跟平台相关的问题,比如不能确定这个对象最后写入到的是一个本地文件、网络流还是JSON这样的中间载体的时候该怎么办?因此我们提供给序列化接口的序列化器这时变得不那么确定了。此外我们也不希望对象的序列化接口明确的知道序列化器是什么,因为这样会让其产生依赖使得对象的使用不独立。综上,我们希望一种模板化的序列化器,只有到对象用到它的时候才会被具体的展开使用。

虚跟模板可以兼得?!

所以我们需要给对象的基类定义一个方法看上去会是:

template<typename Serializer>
virual void Serialize(Serializer& serializer);

但是遗憾的告诉你这是不可能的。为什么?
首先抛开C++的标准不说,单从编译器的实现上面就能否定这一点。虚函数具体是通过虚函数表来表现其多态性的,这个虚函数表实际上是一个函数地址的数组,在建立这个虚函数表的时候所有的虚函数都需要被实现了,这样才能找到有效的函数地址。但另一方面模板方法本身是并不存在的方法,只有在被用到的时候才被具体展开并实例化。因此模板方法的“不确定”性与虚函数的“必须确定”性在理论上便已经矛盾冲突了。

解决方案

首先,为了模板化,不得不先将virtual去掉,这样要求所有对象里面的序列号方法被重新声明为:

template<typename Serializer>
void Serialize(Serializer& serializer);

但是作为序列化方法的调用者,它所能拿到的是所有对象的基类的指针或者引用,它也不能直接调用到子类里面的具体方法。为此目前我能想出的办法就是向下转型,例如:

void Serialize(Base& base)
{
    FooSeraizlier fooSerializer;
    switch(base.GetClassId())
    {
    case DeviedA::ClassID:
        (static_cast<DeviedA*>(&base))->Serialize(fooSerializer);
        break;
    case DeviedB::ClassID:
        (static_cast<DeviedB*>(&base))->Serialize(fooSerializer);
        break;
    case DeviedC::ClassID:
        (static_cast<DeviedC*>(&base))->Serialize(fooSerializer);
        break;
    ......
    }
}

无奈的是类似这样switch case的向下转型必须要手写的,因此当类型过多的时候肯定是很悲剧的,但是如果你有类似python一类的脚本工具来帮你自动生成代码的话,那就放心的使用吧。

Tagged as: 3 Comments
15Nov/110

关于脚本

Posted by Jay

一些关于脚本的琐碎的思绪。

如果说Programming Language是对应基础框架以及底层兼容的偏硬性问题的解决方案,那Script Language便是某种意义上真正面向应用功能的解决方案。Programming Language的初衷是调用平台的各种资源与功能,为了达到最好的效果往往都是贴近底层开发的,在这方面“硬”的东西比较多,而且一旦敲定,便不会容易修改。简而言之,Hardcode大概就是这样一种意思。

太硬的东西无论是开发者还是客户都所不愿意见到的,他们不希望自己买了一辆自行车,轮子只能转到100圈,除非他们都是2B。

于是乎有一种叫Data Driven的概念出现了。大约来讲它属于已经被Hardcode编译成计算机本地指令的程序的外部配置信息。程序的代码已经实现了开发过程中能够预见的大多数功能,这些功能可以接收一定的可变的参数来达到一定程度的“动态性”。这基本上是目前绝大多数项目的一种比较统一的解决方案,无论是application,service或者game。

这种开发模式有一个比较重要的平衡点需要掌握:我们需要将多少功能给做出来,又需要将多少功能可以进行外部的配置。前者过多就会导致过多的硬编码,而后者过多就会导致过于零散的功能以及过于复杂的配置。

无论如何,硬编码是我们是在不想看到的,但是如果功能做得过于原子化,那外部的配置必然会相当复杂。要知道在一个团队里面,负责这部分数据生产的不一定都是城续猿。

有时候我们不能太明白的说明,我们到底想要一个什么样具体的功能,城续猿没办法做出一个抽象的模棱两可的功能然后再暴露一些有限的参数供团队里面其他成员使用,而后者往往也说不清楚自己到底想要什么样的东西。但就算双方能达成协议,通过修改代码来实现新功能以及调试的代价往往也是比较昂贵的。

那么,为何不让他们自己去做?

于是我们需要一种,简单,可用,且能比较高效开发的方式 —— 脚本。

理论上来讲脚本也是一种编码语言,它同样有分支,有循环,有变量,只不过其所在的层次不一样。脚本其实也是Data Driven里面的数据,只不过与单纯的贴图,动画,音效等纯数据相比它更有一种自我描述性。换言之它们能自己说明自己的目的,而不是简单的被贴到某个设置好了UV的模型上面去。

城续猿可以做好一系列的相对独立的功能,这些功能之间可以拼凑组合,相互调用,往往我们会将它们称之为API,最终它们会暴露给脚本使用。

在此基础上,如果说通过Programming Language实现的框架模型是“第一次开发”,那么通过Script Language我们可以在项目中进行“第二次开发”。这也就是一种理想的“准备阶段”往“生产阶段”过渡的方式。案例例如游戏中各种角色的AI,任务,界面UI等等。

待续…

Filed under: 抠腚 No Comments
11Apr/115

关于UI的若干纠结

Posted by Jay

话说最近哥对UI的感情那是各种的错综复杂,似乎业界使用Flash来做In Game UI逐渐成为主流。望着ScaleForm各种强大和稳健,看着自家的Homebrew的UI库,各种兴叹,无奈各种BOSS光环哥只能将错就错了。
既然说到UI不得不提到的就是走哪里都必须很吃香的PS各种版本,做UI总得画个图切个图啥的伐。不过PS各种切,Flash再各种导入,各种费时又费力。不过人家大牌厂家的东西都不是省油的灯,jsfljsx简直就是拯救哥于水火之中的各种救命草。(话说为什么我每次提到这些上面的人貌似有些漠视的感觉......)
简单来说Flash是支持只用导入一张完整的大图,然后基于这张大图创建若干小元件(Symbol,Movie Clip或者Graphic)的,原理就像CSS Spirit。不过如果你想人肉在Flash里面去切那你注定会各种杯具的,因为你会发现Flash的切图功能简直渣到几乎没有。
于是你必须需要jsfl了。我们可以通过jsfl来精确创建各种元件,所谓精切就是说你可以直接像素级别定为图块的各种上下左右而不是靠鼠标凭手感去割,于是:

// Get the document
var doc = fl.getDocumentDOM();
// Get the library
var lib = doc.library;
// Get the selected item in library
var libItem = lib.getSelectedItems()[0];
// Get current fill object
var fill = doc.getCustomFill();
// Set the style to "bitmap", available since CS4
fill.style = "bitmap";
// I don't know what is this neither
fill.bitmapIsClipped = false;
// Set the name of the selected item to the fill object
fill.bitmapPath = libItem.name;
// Well, to get some parameters about this bitmap, you have to instantiate one in the scene first
lib.addItemToDocument({x:0, y:0}, libItem.name);
// And then get the instance
var bitmap = doc.selection[0];
// Then you need to create a mystery matrix
var matrix = bitmap.matrix;
// The spirit's left bound
matrix.tx = -l;
// The spirit's top bound
matrix.ty = -t;
// God knows what does it mean
matrix.a = matrix.d = 20;
// Or Adobe either
matrix.b = matrix.c = 0;
// Set the matrix to the fill object
fill.matrix = matrix;
// And use it
doc.setCustomFill(fill);
// Then it is time to create the spirit
doc.addNewPrimitiveRectangle({left : 0, top : 0, right : r - l, bottom : b - t},0,false,true);

这随之而来就带来了第二个问题,我如何知道每个Spirit的各种上下左右边界,目测加人肉记录似乎可行,不过面对成百上千的各种图的时候,你只会喊破喉咙都没人能救你。 于是jsx这次向你伸出了上帝之手。
只要你在PSD里面将图按照每个Spirit一个图层的方式进行管理,并且每个图层之间相互没有重叠部分,那就可以通过jsx批量的将图层边界信息导出:

// Get the document
var doc = app.activeDocument;
// And prepare to store something
var code = "";
// Traverse all the layers
for(var i = 0; i < doc.layers.length; i++)
{
    // Get current layer's bounds
    var bounds = doc.layers[i].bounds;
    // Concatenate the bounds info of every layer
    if (code != "") code += "|";
    // Make the bounds info of current layer
    code += layer.name + "," + bounds[0] + "," + bounds[1] + "," + bounds[2] + "," + bounds[3];
}

然后你只需要将这生成的code里面的内容,通过各种方式整到之前的jsfl里面去,然后各种华丽你就懂得了。

Filed under: 抠腚 5 Comments
15Feb/113

Protected: 不就是过节么

Posted by Jay

This post is password protected. To view it please enter your password below:


Filed under: 扯淡 3 Comments
27Dec/102

无聊的话无聊的事

Posted by Jay

这确实是一些无聊的话,无聊的事,无聊的以至于笔者都不知道什么时候会随时写不下去。不过随便写写总还是有那么点意思的样子,就当是找不到倾诉,自己说给自己听。
其实,都是一些无聊的思想的自言片语,无聊的没人会去思考这到底有没有意义,也许自己也想不清。其实,这些东西偶尔各种推上面牢骚两句也就过去了,不过不想让太多身边的朋友知道,于是...

好吧,事情大约应该是在年初开始,不过鉴于今天才想到要写点什么,于是先把今天想记录的,给记录吧:

就在刚刚,想到了一些恐怖的事情,甚至,有点恐慌。

然后。

作为一个伪Geek-伪宅-真Coder,我承认我不了解,不熟悉,不在行的事情,确实是太多了。身边的各种人,各种事,看得到的,看不到的,都在用各种方式告诉我一些我懂或不懂的东西。说实话,我只想自己能像一个普通人一样,普普通通的人一样。不知道从什么时候开始,莫名奇妙的变得开始胡思乱想,醒着想,昏着想,做梦什么的也在想。恨不得把所有所知空间时间里面,所有各种事物,各种组合形式统统想完,当然是自己能认知的范围以内。

这,真的是一件挺无聊,又挺恐怖的事情。想到好的事,笑不出来,因为觉得不真实,虚无缥缈;想到坏的事,会不安,觉得怎么也会有可能发生。委婉的说,职业病;直接的说,神经病。

为什么总是要去想下一步会怎样,要去想别人会怎样,要去异想各种乱七八糟的上下文,以至于仍然不能确定方向,找不到办法解决各种问题。于是就被这些不知道从哪里飘来,飞来,滚来的似有非有的东西给困惑,然后仰头叹一声,我又迷茫了。摇摇头看看地,结果还是原地不动,毫无进步,任时间,人群,匆匆流过。

有一点还算明白的事情,就是自己真的不想在这样想下去了。我想过的简单一点,让脑子休息一点。说这么多,想这么多,做这么多,说白了都是在自己掩护罢了。唉...

寂寞,是种病。

Filed under: 扯淡 2 Comments
21Oct/101

0x406D1388

Posted by Jay

神奇的魔数,果断的放弃

Filed under: 抠腚 1 Comment