??xml version="1.0" encoding="utf-8" standalone="yes"?> 事情的v因是Q想找个?Windows ?Mac 的构建方案。第一考虑自然?CMakeQ毕竟基本上是事实标准了?/p>
但是研究了一?Modern CMakeQ也是?target 为核心的理念。但发现看了好几天文档,也折腑և了可用的东西Q但仍然是没梳理清楚什么理c原理。然?CMake 本n语法很复杂Q再加上?target 一套概念,要给 target 讄各种属性之cȝQ有点强?OOP 的感觉……但其实我们只是需要一?include_dir ?lib_dir 而已Q其他都是Q云~ 但如果退回到传统模式Q不?Modern 概念呢,好像可以就Q但W一不去用一个工L最新模式,好像有点不上q的感觉Qpython 2 除外Q;W二QCMake 的两大痛点——语法特立独行、文档晦涩难懂——还是让人有点不爽?/p>
那蟩出来看别的选择呢?目前相对成熟的也只有 Google ?gn+ninja Ҏ了。gn q套东西?Chromium 里是完全配置好的Q用hq算手Q但要是独立拿出来呢Q就没那么便宜了。关键是它的 toolchain 是要自己定义的?/p>
之前q在公司搞客L的时候,大家׃ Chromium 里面?build、build_overrides {等东西全部拷出来,好家伙,几百 MB 甚至?G。但是公叔R嘛,没h干不干净Q怎么快怎么来。后来又看到 Google 自家?Crashpad 里面也用了这套构建,但工具链被简化了一下,?mini_chromium。这个比 Chromium 里的多了,是可以拿q来直接用的Q缺一些配|可以自己加。但是呢Q像我们q种z癖Q仍然是受不鸟的。所以呢Q我们要q干净净的徏立一套工具链?/p>
首先Q我们明定位。gn ?ninja 都是开发机上需要预装的Q不是Y件提供的。Chromium 的搞法是自己提供Qgn 的文档也说让开发者提供工兗但q套思\跟传l的理念是冲H的。同Ӟ自己安装工具成本是比较低的: 自己下蝲的设|到 PATHQ测? 希望做到提供一?git repoQ用?clone 到自己项目的 build 目录Q然后用者只要在 .gn 文g里配|?/p>
可以用我们提供的工具链,?PC 三端q行构徏?/p>
使用者的唯一负担是~写自己?BUILD.gn 首先我们?gn 的文档,以及它的例程 simple_build 里的工具N|: https://gn.googlesource.com/gn/+/HEAD/examples/simple_build/build/toolchain/BUILD.gn q个是可以直接用的,只不q只?linux 端(当然 mac 也能用)。我们再l合 chrome 里的工具N|,q行一些完善?/p>
首先我们了解 gn 体系需要的最配|是什么?/p>
W一Q它需要在根目录写一?.gn 文gQ在里面定义 buildconfigQ指到另一个文Ӟ一般是 W二、BUILDCONFIG.gn 里面需要设|默认工具链Q也是写一?/p>
W三、定义工具链Q如上例?//build/toolchain:gcc?/p>
需要在 build/toolchain 下徏?BUILD.gn 文gQ内Ҏ 最后在 toolchain 里定义各U?tool?/p>
q部分文档在q里Qhttps://gn.googlesource.com/gn/+/main/docs/reference.md#func_tool 单复qC下,可被定义的工hQ?/p>
Q其他的先不看了) 我们来看一?https://gn.googlesource.com/gn/+/HEAD/examples/simple_build/build/toolchain/BUILD.gn 的一些关键配|: 可以看到Qcc ?cxx 执行 command 后,生成 .o 文gQ然后这?.o 文g可以作ؓ alink、solink、link ?inputsQ被它们 command l箋使用Q最后输出静态库、动态库以及可执行文件?/p>
其余属性可以查文档了解含义?/p>
主要配置在这里:https://source.chromium.org/chromium/chromium/src/+/main:build/toolchain/gcc_toolchain.gni 也是 gcc 的,?simple_build 里的大同异Q没有特别的?/p>
主要配置在这里:https://source.chromium.org/chromium/chromium/src/+/main:build/toolchain/apple/toolchain.gni 区别有: 基本上是Ҏ上面分析的要炚w|,最l结果在此:https://github.com/Streamlet/gn_toolchain 新增的一些差异有Q?/p>
增加全局参数 is_debugQ可以在 mac 下生?dSYM 不?python 脚本Q直接是 mac 下加了一?templateQapp_bundleQ用来生?xxx.appQ主要配|来自于 create_bundle 文档里的例子 win 下增加了一些配|集 动?静态链?CRTQ?/build/config/win:console_subsystem?/build/config/win:static_runtime 控制台程序、Win32 E序Q?/build/config/win:console_subsystem?/build/config/win:windows_subsystem q个其实一般情况下用不着Q只要入口函数是 main/WinMainQlink 默认是控制台程?Win32 E序 XP 支持Q?/build/config/win:console_subsystem_xp?/build/config/win:windows_subsystem_xp 具体实现是链接参数加 /SUBSYSTEM:CONSOLE,5.01 ?/SUBSYSTEM:WINDOWS,5.01。关键是后面的版本号 5.01Qؓ了加版本号则必须指定子系l名Uͼ所以分?console_subsystem_xp ?windows_subsystem_xp。又Qxp q里提供了两?subsystem 的配|集Q非 xp 也提供两个?/p>
但是我们没有?_WIN32_WINNT=0x0501、WINVER=0x0501、_USING_V110_SDK71_Q也没有指定必须使用 7.0 版本?SDKQ这些都是非必须的,只要不用?XP 以后d?API 卛_。用者可以在自己?target 里面定义q些宏?/p>
提供一个用案例:https://github.com/Streamlet/gn_toolchain_sample 因ؓ它以 git submodule 形式引用?https://github.com/Streamlet/gn_toolchainQ所?git clone 以后Q需? 然后在根目录执行Q(保 gn ?ninja 已经?PATH 中) 卛_?/p>
Mac 下会额外生成一?objc 目 objc_console_application 以及一?app_bundleQns_application.app?/p>
Win 会额外生成一?Win32 目 win32_application?/p>
Win 下需要先执行一?Visual Studio 带的命o行环境,?VS 2022 Community ?“x64 Native Tools Command Prompt for VS 2022”,cl {工h会可用?/p>
如果要测?XPQ?2位)Q用“x86 Native Tools Command Prompt for VS 2022”进入,CD 到项目目录,执行Q?/p>
我们只用几十 KB 的大完成了跨端支持Q是很轻量的一个配|。如果您觉得实用q认可这U方式,Ƣ迎一hl护、完善?/p>
q次是因为傻逼大微Y改了 System32\IME\shared 里的东西Q导致旧pȝ拯来的文g?System32\IME\shared 的东西不兼容了。解x式很暴力Q从以前的版本复?System32\IME\shared q来?/p> 下蝲Q?a >https://www.streamlet.org/software/mspyforever/ Q原发于 GitHub PagesQ?018-10-13 13:36:04Q?/p> 首先贴个图,大家来一起念台词~ 念完了木有?很激情澎湃义愤填膺有木有Q?/p> q事情最早追溯到前年 8 月的一文章?a href="http://www.shnenglu.com/Streamlet/archive/2014/02/17/188249.html" target="_blank">十个步骤扑֛ Win8 中的微Y拼音C验模?/a>》,其实是手工注册一个COM完事Q只是傻逼大微YL弄了注册表权限来屏蔽Q操作v来略微繁琐。到目前为止QWin8重装pȝ已经不下十次了,每次都是q样手工操作Q我已经厌倦了?/p> 另外q有Win8.1上的问题Q由于傻逼大微Y已经完全删除了文Ӟ没法这么搞了,q从Win8拯文g来,也无法简单注册用。加上我?.1非常非常不感冒,一直没ȝI。前些天看到之前的那文章里 Charles Leigh 回复了两文章(ePig 那篇是原创吧貌似Q感谢)Q提供了解决Ҏ。于是上个周末到现在捣鼓个一键恢复工P方便自己以后重装用,也方便广大微拼党?/p> 微拼党(包括我)孜孜不倦的q求微拼长句模式的情怀Q让我非常感动。希望傻逼大微Y看到我们的心声。别搞什么破词组输入法了Q你搞不q本土这么多厂家的,你的下限也没有本土厂家低Q唯一的优势可能就是没q告没弹H了吧。至于输入算法什么的Q在词组模式里面Ҏ不以体现得太多Q长句模式才是考验啊。回头吧Q?/p> 下蝲面Q?a title="http://www.streamlet.org/Software/MSPYForever/" target="_blank">http://www.streamlet.org/Software/MSPYForever/ CodePlex 目面Q?a title="https://mspyforever.codeplex.com/" target="_blank">https://mspyforever.codeplex.com/ 请微拼党们多传播。有 Bug 及时反馈?/p> 比较的对象是std::regexQ暂时只比较两项Q?/p> 1、解析正则表辑ּ的速度 2、用解析好的正则表辑ּd配字W串的速度?/p> 试代码如下Q?span lang="EN-US"> SECTION_BEGIN(StdRegExParse100000); PERFORMANCE_TEST_BEGIN(StdRegExParse100000); for (int i = 0; i < 100000; ++i) { wregex r; r.assign(L"http://([a-zA-Z0-9\\-]+.)+[a-zA-Z]+/"); } PERFORMANCE_TEST_END(StdRegExParse100000); SECTION_END(); SECTION_BEGIN(xlRegExpParse100000); PERFORMANCE_TEST_BEGIN(xlRegExpParse100000); for (int i = 0; i < 100000; ++i) { RegExp r; r.Parse(L"http://([a-zA-Z0-9\\-]+.)+[a-zA-Z]+/"); } PERFORMANCE_TEST_END(xlRegExpParse100000); SECTION_END(); SECTION_BEGIN(StdRegExMatch100000); { wregex r; r.assign(L"http://([a-zA-Z0-9\\-]+.)+[a-zA-Z]+/"); PERFORMANCE_TEST_BEGIN(StdRegExMatch100000); for (int i = 0; i < 100000; ++i) { regex_match(L"http://w-1.w-2.w-3.streamlet.org/", r); } PERFORMANCE_TEST_END(StdRegExMatch100000); } SECTION_END(); SECTION_BEGIN(xlRegExpMatch100000); { RegExp r; r.Parse(L"http://([a-zA-Z0-9\\-]+.)+[a-zA-Z]+/"); PERFORMANCE_TEST_BEGIN(xlRegExpMatch100000); for (int i = 0; i < 100000; ++i) { r.Match(L"http://w-1.w-2.w-3.streamlet.org/"); } PERFORMANCE_TEST_END(xlRegExpMatch100000); } SECTION_END(); 前两则是分别使用std::wregex和xl::RegExp解析"http://([a-zA-Z0-9\\-]+.)+[a-zA-Z]+/"十万ơ,后两则是拿来匚whttp://w-1.w-2.w-3.streamlet.org/十万ơ?/p> l果如下Q?/p> 匚w速度差很多,解析速度差不多?/p> 考虑到在解析?”?”?”的时候,引入了很多ε边Q于是对那部分做点优化,去除不必要的ε边和节点构造,然后再测试: 可以看到有所提高Q但是解析速度q是跟std:wregex的差很多Q匹配速度有明N先。目前只解析到ε边?NFAQ如果再做状态机转化Q虽然会提高匚w速度Q可是解析速度会进一步下降。因此,一开始就要考虑使用一U更高效的状态机存储Ҏ?/p> q两天着凉生病了Q好隑֏啊…?/p> Q所有内定w?VC ~译q_Q?/p> 一、x86 二、x64 64位编译环境下Q可以指?__cdecl、__stdcall、__fastcallQ但是编译器会忽略它们。两个显C指定了不同调用U定的函C构成重蝲Q而构成重定义错误?/p> 可是QC 风格的到底有什么坏处?C++的这?cast 又有什么好处呢Q?/p> 昨天以前Q我q这?cast 是什么都不知道(很惭愧)。昨天因为同事们提到qg事,于是小研究了一下。一些实验代码如下: 1、无l承的类型{?/p> class A { }; class B { public: operator A() { return A(); } }; int main() { B b; A a = (A)b; // 执行 operator A() A a2 = static_cast<A>(b); // 执行 operator A() A a3 = dynamic_cast<A>(b); // 不允?/font> A a4 = reinterpret_cast<A>(b); // 不允?/font> A a5 = const_cast<A>(b); // 不允?/font> return 0; } 2、const_cast struct A { int m; A(int m = 0) : m(m) { } }; int main() { const A a; A a2 = (A)a; // 允许Q?/font>(A) 有没有都一Pa2 是个新变?/font> a2.m = 1; // a2 的改变不影响 a A &a3 = (A &)a; // 允许 a3.m = 2; // 影响 a // A &a4 = a; // 不允许,const 限定起作用了 A *pa5 = (A *)&a; // 允许 pa5->m = 3; // 影响 a // A *pa6 = &a; // 不允许,const 限定起作用了 // A aa2 = const_cast<A>(a); // 不允?/font> A &aa3 = const_cast<A &>(a); // 允许 aa3.m = 2; // 影响 a A *paa5 = const_cast<A *>(&a); // 允许 paa5->m = 3; // 影响 a const int i = 0; const int &i2 = i; const int *pi3 = &i; // int j = const_cast<int>(i); // 不允?/font> int &j2 = const_cast<int &>(i2); // 允许 int *pj3 = const_cast<int *>(pi3); // 允许 return 0; } 从第1点的试验Q加上外界资料的说明Q看上去const_case 只允许具有不同cv限定W的同类型之间的转换? 值得注意的是Q如果类型A不是指针或引用,不能使用const_castQ用了也无意义Q见 A a2 = (A)a q一行) ?const_cast 可以使用的情形,(T)value 形式都可以用,(T)value 在功能上完全覆盖 const_cast? 2、reinterpret_cast class A { public: operator int *() { return nullptr; } }; int main() { int i = 0; double d = 1.0; int *p = nullptr; // int di = reinterpret_cast<int>(d); // 不允?/font> int pi = reinterpret_cast<int>(p); // 允许 // int pi2 = static_cast<int>(p); // 不允?/font> // double id = reinterpret_cast<double>(i);// 不允?/font> // double pd = reinterpret_cast<double>(p);// 不允?/font> int *ip = reinterpret_cast<int *>(i); // 允许 // int *ip2 = static_cast<int *>(i); // 不允?/font> // int *dp = reinterpret_cast<int *>(d); // 不允?/font> A a; int *pa = (int *)a; // 允许 int *pa2 = static_cast<int *>(a); // 允许 // int *p2 = reinterpret_cast<int *>(a); // 不允?/font> return 0; } 看上去,reinterpret_cast 可以理解为在指针和数g间{换的一U方式,无关Mq算W重载,仅仅把指针{为字面|或者把数字转ؓ指针Q{换的q程中值没有Q何改变,只是告诉~译器不要报cd不匹配而已? 另外Q在reinterpret_cast可以使用的情形,static_cast 是不可以使用的,除非定义了相应的cd转换q算W? ?reinterpret_cast 可以使用的情形,(T)value 的方式同样可以完全胜任,(T)value 在功能上完全覆盖 reinterpret_cast? dynamic_cast 我自认ؓq是理解的,׃试了? lgQ我的理解如下: 1?/b>static_cast + const_cast + reinterpret_cast = (T)value C++ 把原?/b>C风格的的q三?/b>cast拆分成了三个Q三者相互正交。大多数情况下,应该?/b> static_cast 在取代着 (T)valueQ只是在去除 cv 限定W的时候,换用 const_castQ在取指针字面值的时候,换用 reinterpret_cast。类型{换运符 operator T() ?static_cast 负责执行?/b> 2?/b>dynamic_cast ?C++ 新增的,用于多态的情ŞQ且只允许{换具有多态关pȝl承树上的类型的指针和引用,不允许{换类型本w。它不是针对 (T)value而出现的Q两者没有Q何竞争关p,只是取决于不同的需求?/b> Q不知这L解是否正,h评指正~Q? 至于|上推崇用新写法Q是不是Z更细化而容易理解?有没有什么是 (T)value 做不到?*_cast 能做到的Q或者反q来Q?构徏pȝ安装
gn --version
以及 ninja --version
Q能q行卛_目标
buildconfig = "//build/BUILDCONFIG.gn"
工具链搭?/h2>
基础概念
buildconfig = "//build/BUILDCONFIG.gn"
set_default_toolchain("//build/toolchain:gcc")
toolchain("gcc") {
# ...
}
工具链中的工?/h3>
"cc": C ~译?br />
"cxx": C++ ~译?br />
"cxx_module": 支持 module ?C++ ~译?br />
"objc": Objective C ~译?br />
"objcxx": Objective C++ ~译?br />
"rc": Windows 资源脚本~译?br />
"asm": 汇编?br />
"swift": Swift ~译?/li>
"alink": 静态库链接?br />
"solink": 动态库链接?br />
"link": 可执行文仉接器toolchain("gcc") {
tool("cc") {
command = "gcc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
outputs = [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
# ...
}
tool("cxx") {
command = "g++ -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
outputs = [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
# ...
}
tool("alink") {
command = "rm -f {{output}} && ar rcs {{output}} {{inputs}}"
outputs = [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
# ...
}
tool("solink") {
command = "g++ -shared {{ldflags}} -o $sofile $os_specific_option @$rspfile"
outputs = [ sofile ]
# ...
}
tool("link") {
command = "g++ {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}"
outputs = [ outfile ]
# ...
}
tool("stamp") {
command = "touch {{output}}"
}
tool("copy") {
command = "cp -af {{source}} {{output}}"
}
}
Ҏ Chromium 中的配置
Linux
Mac
Win
建立我们的工具链
gn gen out --args="is_debug=true"
传入。默?is_debug ?falseQ开启所有优化?/p>
$ld ... && dsymutil ... && strip
使用案例
git submodule update --init
一下?/p>
gn gen out
ninja -C out
gn gen out --args="target_cpu=\"x86\""
ninja -C out
ȝ
]]>
]]>
]]>
]]>
]]>
]]>
]]>
]]>
]]>
]]>
]]>
]]>
]]>
]]>
]]>
名称 传参方式 栈清?/td> C 语言函数重命名(例:int func(int, double)Q?/td> __cdecl 从右臛_压栈 主调函数 前面加“_”(_funcQ?/td> __stdcall 从右臛_压栈 被调函数 前面加“_”,后面加“@”再加参数十q制字节敎ͼ_func@12Q?/td> __fastcall 前两个不大于DWORD长度的参C左至叛_别存?ECX、EDXQ其余从双左压?/td> 被调函数 前面加“@”,后面加“@”再加参数十q制字节敎ͼ@func@12Q?/td> __thiscall ECX ?thisQ其余从双左压?/td> 被调函数 仅用?C++
名称 传参方式 栈清?/td> __fastcall 前四个整?点数放?RCX/XMM0、RDX/XMM1、R8/XMM2、R9/XMM3Q其余压栈?br>如果?4 个参数分别ؓ int、float、long、doubleQ它们将分别被存?RCX、XMM1、R8、XMM3 被调函数
左值和叛_?/font>
1 Every expression is either an lvalue or an rvalue.
表达式不是左值就是右倹{?/font>
2 An lvalue refers to an object or function. Some rvalue expressions—those of class or cv-qualified class type—also refer to objects. 47)
左值是指一个对象或者函数。某些右|那些带或不带cv限定W的cȝ型)也是对象?/font>
3 [Note:
some built-in operators and function calls yield lvalues. [Example: if E is an expression of pointer type, then *E is an lvalue expression referring to the object or function to which E points. As another example, the function
int& f();
yields an lvalue, so the call f() is an lvalue expression. ] ]
[注意Q某些内|运符以及一些函数调用会q回左倹{[?Q如?E 是一个指针类型的表达式,那么 *E 是一个左D辑ּQ指C指?E 所指向的那个对象或者函数。例2Q函?br>int& f();
q回左|所以函数调?f() 是一个左D辑ּ。] ]
4 [Note: some built-in operators expect lvalue operands. [Example: built-in assignment operators all expect their left hand operands to be lvalues. ] Other built-in operators yield rvalues, and some expect them. [Example: the unary and binary + operators expect rvalue arguments and yield rvalue results. ] The discussion of each built-in operator in clause 5 indicates whether it expects lvalue operands and whether it yields an lvalue. ]
[注意Q某些内|运符需要左值操作数。[例:所有内|的赋D符的左操作数都必须是左倹{] 有些内置q算W会q回叛_结果,有些会需要右值操作数。[例:一元运符?”和二元q算W?”都需要右值操作数Qƈ且返回右值结果。] 我们在条?5 中会Ҏ有内|运符q行讨论Q指出它们的操作数和q回l果是左D是右倹{]
5 The result of calling a function that does not return a reference is an rvalue. User defined operators are functions, and whether such operators expect or yield lvalues is determined by their parameter and return
types.
调用q回gؓ非引用类型的函数Q结果是叛_{用戯定义的运符也是函数Q这些运符的操作数以及l果是左D是右|取决于(q算W重载)函数的参数和q回值类型?/font>
6 An expression which holds a temporary object resulting from a cast to a nonreference type is an rvalue (this includes the explicit creation of an object using functional notation (5.2.3)).
寚w引用cd的{换表辑ּQ包括用函数式的记可行显C创建)是右|它将q回一个时对象?/font>
__________________
47) Expressions such as invocations of constructors and of functions that return a class type refer to objects, and the implementation can invoke a member function upon such objects, but the expressions are not lvalues.
有些表达式,比如调用构造函数的表达式,以及调用q回cȝ型的函数的表辑ּQ它们也是对象,且可以对它们调用成员函数Q但q种表达式不是左倹{?/font>
____________________________________华丽的分늬____________________________________
7 Whenever an lvalue appears in a context where an rvalue is expected, the lvalue is converted to an rvalue; see 4.1, 4.2, and 4.3.
如果在一个需要右值的场合出现了左|q个左值将被{换成叛_{?br>?4.1?.2?.3?/font>
8 The discussion of reference initialization in 8.5.3 and of temporaries in 12.2 indicates the behavior of lvalues and rvalues in other significant contexts.
12.2 中关于引用的初始化和关于临时对象的讨论,也指Z左右值在其他重要场合的行为?/font>
9 Class rvalues can have cv-qualified types; non-class rvalues always have cv-unqualified types. Rvalues shall always have complete types or the void type; in addition to these types, lvalues can also have incomplete types.
cȝ型的叛_可以具?cv 限定W;非类cd的右g能被 cv 限定W修饰。右值通常是完整类型或?void cdQ而对于左值来_除了完整cd?void cd外,q可以是不完整类型?/font>
10 An lvalue for an object is necessary in order to modify the object except that an rvalue of class type can also be used to modify its referent under certain circumstances. [Example: a member function called for an object (9.3) can modify the object. ]
通常情况下,如果要修改一个对象,它必L左倹{但在某些特定的场合Q右值Ş式的cd象也可以被修攏V[例:调用一个对象的成员函数Q?.3Q,可以修改对象本n。]
11 Functions cannot be modified, but pointers to functions can be modifiable.
函数Q对象)不能Q在q行Ӟ被修改,但是函数指针可以?/font>
12 A pointer to an incomplete type can be modifiable. At some point in the program when the pointed to type is complete, the object at which the pointer points can also be modified.
指向一个不完整cd的指针可能是可修改的。当q个被指向的cd某时某刻成ؓ完整cd后,q个指针所指向的那个对象也是可修改的?/font>
13 The referent of a const-qualified expression shall not be modified (through that expression), except that if it is of class type and has a mutable component, that component can be modified (7.1.5.1).
?const 限定的表辑ּ所对应的对象不能(通过该表辑ּQ被修改Q除非这个对象是cȝ型ƈ且含?mutable 成员Q此时该 mutable 成员可以被修攏V?/font>
14 If an expression can be used to modify the object to which it refers, the expression is called modifiable. A program that attempts to modify an object through a nonmodifiable lvalue or rvalue expression is illformed.
如果一个表辑ּ可以被用来修Ҏ表达式对应的对象Q那么这个表辑ּ被称为可修改的。企N过一个不可修改的左D右D辑ּM改一个对象,是非法的?/font>
15 If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined 48):
?the dynamic type of the object,
?a cv-qualified version of the dynamic type of the object,
?a type that is the signed or unsigned type corresponding to the dynamic type of the object,
?a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of
the object,
?an aggregate or union type that includes one of the aforementioned types among its members (including,
recursively, a member of a subaggregate or contained union),
?a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
?a char or unsigned char type.
如果E序通过下列cd之外cd的左D问一个对象的|其行为是未定义的Q?/font>
——对象的动态类型;
——CV 限定的对象动态类型;
——对象的动态类型对应的有符h无符L型;
——CV 限定的对象动态类型有W号或无W号cdQ?br>——一个成员中含有聚合或者联合类型的聚合或者联合类型(包括递归形式的定义以及成员中有子聚合cd或者包含一个联合)
——对象的动态类型的基类cdQ可以被 cv 限定W修饎ͼ
——char ?unsigned char cd
__________________
48) The intent of this list is to specify those circumstances in which an object may or may not be aliased.
l出q䆾列表的目的是指明一些特定情形,在这些情形下对象可能被重叠,但也有可能不是,我们无法预知?/font>
____________________________________华丽的分隔符____________________________________
以上Q求指正Q包括理解误区和语言l织不合理的Q欢q指出~ ^_^
排版斚w的大概有Q如何羃q,如何使用I格、换行,{等。命名方面的包括变量、函数、类、文件的取名{等。书写逻辑斚w的就比较多了Q可能包括:
是否全面使用异常、出错处理资源清理如何组l、如何利用编译提C防止常见错误…?/p>
一旉列不全。网上常见的文档我会参考的。除此之外,想从大家q里征求下,以上几个大方面之外,q有没有比较重要的方面?大家日常工作中有没有遇到一些特别希望别Z使用和自׃L方式做的事?以及Q哪些规定比较容易被推动Q哪些规定不Ҏ被推动?如果有一个规则强加在你头上,你会有怎样的心理?{等…?/p>
如果您有xQ请回复下,我们讨论讨论^_^
----------
Z再问个问题,Windows 上的开发,大家喜欢动态链?CRTQ?MD?MDdQ?q是静态链?CRTQ?MT?MTdQ?Z么?个h們于哪U?在公叔R又是怎样做的Q?/p>
E微解释下,因ؓ有可能有Z误会Q放新线E里面去不就可以了?q没有解决问题。如此的话,你那个线E函数怎么写?或者线E函数里调用的某个Q务函数怎么写?MQ多U程虽然L出现在这些问题的解决Ҏ中,但不是多U程解决了这个问题。嗯……不知道说清楚了没?
目前我心里的{案只有q一U模式:
bool DoTask(HANDLE hQuitSignal)
{
while (!QuitCondition)
{
if (WaitForSingleObject(hQuitSignal, 0) == WAIT_OBJECT_0)
{
return false;
}
// Do something
}
return true;
}
其中Q?/ Do something”部分要l化到瞬间执行完成的l度?/p>
但是我很困惑的是Q如果这些Q务很J重Q难道我必须每进行一些操作就 if (WaitForSingleObject(hQuitSignal, 0) == WAIT_OBJECT_0) 查下吗?q样岂不是这U检代码充斥在d中了Q?/p>
不知各位有何l验和体会,求教~
很多时候我们需要返?传入一堆参敎ͼ所以不得不每次定义一些ؓ了数据传输的l构。Tuple 是用来解决q一问题的,它提供即时构造一个这Ll构体的功能。而所付出的代hQ失各个成员的明确含义Q只留下成员的序受?/p>
两个元素?Tuple 是 PairQ如 std::pair。下面我们来建立针对有限个元素的 Tuple。对于一个元素、两个元素、三个元素,我们可以分别如下实现Q?/p>
template <typename T0>
struct Tuple
{
T0 _0;
};
template <typename T0, typename T1>
struct Tuple
{
T0 _1;
T1 _1;
};
template <typename T0, typename T1, typename T2>
struct Tuple
{
T0 _1;
T1 _1;
T2 _2;
};
但是q三个写在一P׃出错。ؓ此,我们可以先定义一个含_多模版参数的 TupleQ然后上面三个分别作为偏特化版本Q?/p>
template <typename T0 = NullType, typename T1= NullType, typename T2= NullType, typename T3= NullType, typename T4= NullType>
struct Tuple;
template <typename T0>
struct Tuple<T0>
{
T0 _0;
};
template <typename T0, typename T1>
struct Tuple<T0, T1>
{
T0 _1;
T1 _1;
};
template <typename T0, typename T1, typename T2>
struct Tuple<T0, T1, T2>
{
T0 _1;
T1 _1;
T2 _2;
};
如果手写的话Q这也可以。如果不手写Q我们可以l用之前?a href="http://www.shnenglu.com/Streamlet/archive/2011/01/17/138693.html" target="_blank">C++ ?Function 对象的实玎ͼ下)》中的宏循环Ҏ。此Ҏ的一个正式版本见 xlMacros.h?/p>
定义带默认?NullType 的模版参数声明序列如下:
#define XL_TUPLE_TYPENAME_DECLARE_NT_PATTERN(n) typename T##n = NullType
#define XL_TUPLE_TYPENAME_DECLARE_NT(n) XL_REPZ(XL_TUPLE_TYPENAME_DECLARE_NT_PATTERN, n, XL_COMMA)
它将被展开为: typename T0 = NullType, typename T1 = NullType, typename T2 = NullType, ? typename Tn = NullType
定义不带默认值的模版参数声明序列如下Q?/p>
#define XL_TUPLE_TYPENAME_DECLARE_PATTERN(n) typename T##n
#define XL_TUPLE_TYPENAME_DECLARE(n) XL_REPZ(XL_TUPLE_TYPENAME_DECLARE_PATTERN, n, XL_COMMA)
它将被展开为:typename T0, typename T1, typename T2, ? typename Tn
定义模版参数使用序列如下Q?/p>
#define XL_TUPLE_TYPENAME_LIST_PATTERN(n) T##n
#define XL_TUPLE_TYPENAME_LIST(n) XL_REPZ(XL_TUPLE_TYPENAME_LIST_PATTERN, n, XL_COMMA)
它将被展开?T0, T1, T2, ? Tn
定义成员变量声明序列如下Q?/p>
#define XL_TUPLE_MEMBER_DECLARE_PATTERN(n) T##n _##n;
#define XL_TUPLE_MEMBER_DECLARE(n) XL_REPZ(XL_TUPLE_MEMBER_DECLARE_PATTERN, n, XL_NIL)
它将被展开为:T0 _0; T1 _1; T2 _2; ?Tn _n;
现在我们开始组装:
#ifndef XL_TUPLE_DEFINE_MAX
#define XL_TUPLE_DEFINE_MAX 20
#endif
template <XL_TUPLE_TYPENAME_DECLARE_NT(XL_INC(XL_TUPLE_DEFINE_MAX))>
struct Tuple;
template <XL_TUPLE_TYPENAME_DECLARE(n)>
struct Tuple<XL_TUPLE_TYPENAME_LIST(n)>
{
XL_TUPLE_MEMBER_DECLARE(n)
};
其中后一个还带有宏参?n。我们将q整一个定义成宏,然后q行宏@环:
#define XL_TUPLE_IMPLEMENT_PATTERN(n) \
\
template <XL_TUPLE_TYPENAME_DECLARE(n)> \
struct Tuple<XL_TUPLE_TYPENAME_LIST(n)> \
{ \
XL_TUPLE_MEMBER_DECLARE(n) \
}; \
#define XL_TUPLE_IMPLEMENT(n) XL_REPY(XL_TUPLE_IMPLEMENT_PATTERN, n, XL_NIL)
之后再用这个宏Q?/p>
XL_TUPLE_IMPLEMENT(XL_TUPLE_DEFINE_MAX)
到此为止Q上文一开始提出的 Tuple 已经实现Qƈ支持到最大约 20 个元素左叟?/p>
然后我们可以考虑增加各种方便使用的功能?/p>
#define XL_TUPLE_NOT_EQUAL_PATTERN(n) this->_##n != that._##n
#define XL_TUPLE_NOT_EQUAL(n) XL_REPZ(XL_TUPLE_NOT_EQUAL_PATTERN, n, ||)
#define XL_TUPLE_LITTER_PATTERN(n) if (this->_##n < that._##n) \
{ \
return true; \
} \
else if (that._##n < this->_##n) \
{ \
return false; \
}
#define XL_TUPLE_LITTER(n) XL_REPZ(XL_TUPLE_LITTER_PATTERN, n, XL_NIL)
#define XL_TUPLE_GREATER_PATTERN(n) if (this->_##n > that._##n) \
{ \
return true; \
} \
else if (that._##n > this->_##n) \
{ \
return false; \
}
#define XL_TUPLE_GREATER(n) XL_REPZ(XL_TUPLE_GREATER_PATTERN, n, XL_NIL)
同时 Tuple 中也增加相应的函敎ͼ卛_?/p>
最l代码见 xlTuple.hQ这里不贴了?/p>
请多多指正?/p>
template <typename T1, typename T2>
XXX Min(T1 t1, T2 t2)
{
return (t1 < t2 ? t1 : t2);
}
其中 XXX 是我们要推导出的cd?
以下是一个失败的试?
我记?Loki 里有关于如何判断某个cd能否隐式转换为另一个类型的东西Q大意如下:
template <typename T, typename U>
class Conversion
{
private:
typedef char Small;
class Big { char XXX[2]; };
static Small Test(U);
static Big Test(...);
static T MakeT();
public:
enum
{
Exists = (sizeof(Test(MakeT())) == sizeof(Small)),
};
};
如此QConversion<T, U>::Exists p判断 T ?U 的{换是否存在了?
然后再搞个选择Q?
template <bool Condition, typename TypeIfTrue, typename TypeIfFalse>
struct Select
{
typedef TypeIfFalse Type;
};
template <typename TypeIfTrue, typename TypeIfFalse>
struct Select<true, TypeIfTrue, TypeIfFalse>
{
typedef TypeIfTrue Type;
};
最后,再来个:
struct NullType;
template <typename T, typename U>
struct CommonType
{
typedef typename Select<Conversion<T, U>::exists,
U,
typename Select<Conversion<U, T>::exists,
T,
NullType>::Type
>::Type Type;
};
那么 CommonType<T1, T2> 是 T1 ?T2 之间哪个是他们的共有cd了?
试Q?
int main()
{
CommonType<int, double>::Type m = 0;
return 0;
}
调试Q确?m ?double 的。但是反q来?CommonType<double, int>::Type m = 0;Qm 却是 int 的?
q说明这套机制一开始就有问题,Test(U) ?Test(? 两个重蝲函数中,Test(? 不会在需?double ?int 时胜出。这是第一个问题?
W二个问题,当写下如下代码的时候:
template <typename T1, typename T2>
CommonType<T1, T2>::Type Min(T1 t1, T2 t2)
{
return (t1 < t2 ? t1 : t2);
}
~译无法通过?u>原因是返回类型中?CommonType 中的模板参数 T、U 无法接受此时q不能确定的 T1、T2?/u>
(更正Q不是这个原因,q回cd前加 typename 卛_。现在问题还是第一个问题?
h各位Q有没有什么方法做刎ͼƢ迎指教~
C++ 0x 中就能很方便做到了:
template <typename T1, typename T2>
auto Min(T1 t1, T2 t2) -> decltype(t1 + t2)
{
return (t1 < t2 ? t1 : t2);
}
int main()
{
int a = 2;
double b = 1.0;
auto m = Min(a, b);
return 0;
}
不知大家有没有发玎ͼ当?VS 来调试代码的时候,那些 STL 容器的信息会以比较看得懂的方式显C出来:
而我们自己写的,它只能按照数据成员来昄Q如果数据结构稍微复杂点Q看q些直接昄的内容得到的有用信息׃很少了:
那么Q是否有办法能让 IDE 按照我们设想的方式来昄数据呢?{案是肯定的。这个配|就位于 autoexp.txt 中(具体路径?X:\Program Files\Microsoft Visual Studio 10.0\Common7\Packages\Debugger\autoexp.datQ如果是 VS 不同版本Q“Microsoft Visual Studio 10.0”中的版本号换成其他的即可,VS 2005 以上版本都支持)?/p>
在里面搜?vectorQ可以找C用于 vector 的调试信息显C方式的语句Q?/p>
std::vector<*>{ children ( |
q个语法的详l官方说明好像没有,不过大体上可以猜出来?/p>
W一?std::vector<*> 是类型,说明下面的内定w?std::vectorQƈ且适用于Q意模版参数。紧接着是一个大括号括v来的D落?/p>
preview 开始的那一D表C当该变量单行显C的时候该如何昄Q也是下图W二列的样子Q?br>
IDE 会依ơ显C?preview(#( ?)) 括号内的以逗号分隔的内容,加引L会原hC,变量?$e 引用Q将 $e 视ؓ该类型的一个变量)?/p>
除了直接引号Q变量的q算l果外,q里q可以写一些高U点的玩意儿Q如 #arrayQ?listQ?tree?/p>
#array 的格式ؓQ?br>#array(
expr: ?,
size: ?br>)
其中 expr 里可以?$iQ?i 为元?indexQsize 表示元素个数。最l的l果为:
$i=0时的expr, $i=1时的expr, ? $i=size时的expr?/p>
假设有一个结构:
struct Vector
{
int *pData;
int nCount;
};
其中 p 是指向一?count ?int 的内存。如果要依次昄q?count 个数字,preview 中应该写Q?br>preview (
#(
#array(
expr: $e.pData[$i],
size: $e.nCount
)
)
)
#list 的格式ؓQ?br>#list(
head: ?,
size: ?,
next: ?br>) : ?br>其中 head 是指向第一?Node 的指针,size 表示元素个数Qnext 表示 Node 中指向下一?Node 的分量名Q最后冒号后面还要写一?Node 中的值分量,也就是要昄的那个变量?/p>
假设有结构:
struct ListNode
{
int nData;
ListNode *pNext;
};
struct List
{
ListNode *pHeadQ?br> int nCount;
};
preview 的写法ؓQ?br>preview (
#(
#list(
head: $e.pHead,
size: $e.nCount,
next: pNext
) : $e.nData // 注意Q这里的 $e 代表 ListNodeQ上面两?$e 都代?List
)
)
#tree的格式ؓ
#tree(
head: ?
left: ?
right: ?
size: ?br>) : ?br>其中 head 是指向根节点的指针,left ?right 分别是指向左叛_树的分量名,size 表示元素个数Q最后冒号后面写节点中的值分量。IDE会对整棵树做中序遍历?/p>
假设有结构:
struct TreeNode
{
int nData;
TreeNode *pLeft;
TreeNode *pRight;
};
struct Tree
{
TreeNode *pRoot;
int nCount;
};
preview 的写法ؓQ?br>preview (
#(
#tree(
head: $e.pRoot,
left: pLeft,
right: Right,
size: $e.nCount
) : $e.nData // 注意Q这里的 $e 代表 TreeNodeQ上面两?$e 都代?Tree
)
)
preview 的格式就到此。接下来?childrenQ它用于描述点击变量左边的加号后Q展开的内Ҏ么昄Q如图:
IDE 会依ơ显C?children(#( ?)) 括号内的以逗号分隔的内容,每个昄Z行。刚才的 #array, #list, #tree 都可以用Q会昄成第一列烦引第二列值的样子?br>另外可以?#(first, second) 的格式,first 会原hC在W一列,second 会求值显C在W二列?/p>
了解以上q些内容Q我们已l可以针?STL 的那些数据结构做自定义显CZQ对一些别的数据结构作单的自定义显CZ不难?/p>
最后给个效果图Q定义了我自q那些容器的显C方式。怎么P看上M定比开头给出的那个冷冰冰的样子好很多吧Q?/p>
C++ ?Function 对象的实玎ͼ上)
C++ ?Function 对象的实玎ͼ下)
上篇中,我们实现了一个支?R () 型函数的 Function。补充说明一下,在我们对成员函数的支持中Q我们是q样定义的:
template <typename R, typename T>
class MemberFunction0 : public FunctionBase0<R>
{
private:
R (T::*m_pMemFun)();
T *m_pObj;
};
Loki Ҏ在著作中提醒我们Q这里的 T 最好不要是函数cdQ改为函数指针类型,如此该类的支持范围将扩大。如下:
template <typename R, typename P, typename T>
class MemberFunction0 : public FunctionBase0<R>
{
public:
R Invoke()
{
return (m_pObj->*m_pMemFun)();
}
public:
MemberFunction0(P pObj, R (T::*pMemFun)())
: m_pObj(pObj), m_pMemFun(pMemFun)
{
}
private:
R (T::*m_pMemFun)();
P m_pObj;
};
于是QP ?T 的关pM那么紧密了,P 不一定非?T* 不可Q也可以是诸?SmartPtr<T> 之类的玩意儿。原本只支持传入一个对象和该对象的成员函数的,现在变成传入一个具有指针概늚东东和一个成员函敎ͼ只要q个“指针”用运符 ?gt; 去调用那个成员函数合乎语法即可?/p>
接下来,我们来扩展这?FunctionQ以支持拥有数目在给定上限内的Q意参数的函数?/p>
我们先来手工写一下,看看如何支持带一个参数的函数。首先定义一个虚基类Q?/p>
template <typename R, typename T0>
class FunctionBase1
{
public:
virtual R Invoke(T0) = 0;
virtual ~FunctionBase1() {}
};
实现两个版本Q分别支持非成员函数和成员函敎ͼ
template <typename R, typename T0, typename T>
class Function1 : public FunctionBase1<R, T0>
{
public:
R Invoke(T0 v0)
{
return m_Fun(v0);
}
public:
Function1(const T &fun)
: m_Fun(fun)
{
}
private:
T m_Fun;
};
template <typename R, typename P, typename T, typename T0>
class MemberFunction1 : public FunctionBase1<R, T0>
{
public:
R Invoke(T0 v0)
{
return (m_pObj->*m_pMemFun)(v0);
}
public:
MemberFunction1(P pObj, R (T::*pMemFun)(T0))
: m_pObj(pObj), m_pMemFun(pMemFun)
{
}
private:
R (T::*m_pMemFun)(T0);
P m_pObj;
};
增加一个函数引用萃取的偏特化版本:
template <typename RetType, typename T0>
struct FunctionTraits<RetType (T0)>
{
typedef RetType (&ParamType)(T0);
};
增加一?Function cȝ偏特化版本:
template <typename R, typename T0>
class Function<R (T0)>
{
public:
template <typename T>
Function(const T &fun)
: m_pFunBase(new Function1<R, T0, typename FunctionTraits<T>::ParamType>(fun))
{
}
template <typename P, typename T>
Function(P pObj, R (T::*pMemFun)(T0))
: m_pFunBase(new MemberFunction1<R, P, T, T0>(pObj, pMemFun))
{
}
~Function()
{
delete m_pFunBase;
}
R operator ()(T0 v0)
{
return m_pFunBase->Invoke(v0);
}
private:
FunctionBase1<R, T0> *m_pFunBase;
};
现在Q我们可以跑一下测试代码了Q?/p>
Function<int (int)> f1(&intfun1);
Function<int (int)> f1_(intfun1);
Function<int (int)> f2(intfunctor1);
Function<int (int)> f3(&test, &Test::intmem1);
f1(1);
f1_(1);
f2(2);
f3(3);
当然Qvoid 函数也是支持的?/p>
观察上面的这些代码,和我们在上一中的代码高度一_不同的是那些模版参数、偏特化参数、函数调用参数等地方?/p>
假如有这么一l宏Q?br>TYPENAME_DECLARE(n) 被定义ؓ typename T0, typename T1, ? typename Tn
TYPENAME_LIST(n) 被定义ؓ T0, T1, ? Tn
TYPENAME_VARIABLE(n) 被定义ؓ T0 v0, T1 v1, ? Tn vn
VARIABLE_LIST(n) 被定义ؓ v0, v1, ? vn
那么我们可以使用一?n 写出支持所有具有参数的函数?Function 了。我们抛弃掉上面?1 pd的所有类Q仅保持上篇留下来的代码Q然后利用上?4 个宏所有数字尾巴去掉,于是代码变成Q?/p>
template <typename R, TYPENAME_DECLARE(n)>
class FunctionBase_##n
{
public:
virtual R Invoke(TYPENAME_LIST(n)) = 0;
virtual ~FunctionBase_##n() {}
};
template <typename R, TYPENAME_DECLARE(n), typename T>
class Function_##n : public FunctionBase_##n<R, TYPENAME_LIST(n)>
{
public:
R Invoke(TYPENAME_VARIABLE(n))
{
return m_Fun(VARIABLE_LIST(n));
}
public:
Function_##n(const T &fun)
: m_Fun(fun)
{
}
private:
T m_Fun;
};
template <typename R, typename P, typename T, TYPENAME_DECLARE(n)>
class MemberFunction_##n : public FunctionBase_##n<R, TYPENAME_LIST(n)>
{
public:
R Invoke(TYPENAME_VARIABLE(n))
{
return (m_pObj->*m_pMemFun)(VARIABLE_LIST(n));
}
public:
MemberFunction_##n(P pObj, R (T::*pMemFun)(TYPENAME_LIST(n)))
: m_pObj(pObj), m_pMemFun(pMemFun)
{
}
private:
R (T::*m_pMemFun)(TYPENAME_LIST(n));
P m_pObj;
};
template <typename RetType, TYPENAME_DECLARE(n)>
struct FunctionTraits<RetType (TYPENAME_LIST(n))>
{
typedef RetType (&ParamType)(TYPENAME_LIST(n));
};
template <typename R, TYPENAME_DECLARE(n)>
class Function<R (TYPENAME_LIST(n))>
{
public:
template <typename T>
Function(const T &fun)
: m_pFunBase(new Function_##n<R, TYPENAME_LIST(n), typename FunctionTraits<T>::ParamType>(fun))
{
}
template <typename P, typename T>
Function(P pObj, R (T::*pMemFun)(TYPENAME_LIST(n)))
: m_pFunBase(new MemberFunction_##n<R, P, T, TYPENAME_LIST(n)>(pObj, pMemFun))
{
}
~Function()
{
delete m_pFunBase;
}
R operator ()(TYPENAME_VARIABLE(n))
{
return m_pFunBase->Invoke(VARIABLE_LIST(n));
}
private:
FunctionBase_##n<R, TYPENAME_LIST(n)> *m_pFunBase;
};
当然上面q样子的代码是没法跑的咯。如果我们将整段代码定义Z个宏 BODY(n)Q然后用cM刚才四个宏的方式定义?FUNCTION_IMPLEMENT(n)Q得它的含义ؓ BODY(0), BODY(1), ? BODY(n)Q所有工作就都完成了。最后只需要丢下一?FUNCTION_IMPLEMENT(20)Q就可以支持 0 ?21 个参C?/p>
最后归lؓQ如何用宏搞出“T0, T1, ? Tn?的Ş式?/p>
暴力点,我们可以q样Q?/p>
#define T_0 T0
#define T_1 T_0, T1
#define T_2 T_1, T2
#define T_3 T_2, T3
#define T_4 T_3, T4
#define T_5 T_4, T5
#define T_6 T_5, T6
#define T_7 T_6, T7
#define T_8 T_7, T8
#define T_9 T_8, T9
#define T(n) T_##n
q样子,对于上面四个宏可以,但是对于最后的 X(n)Qh工代码量q是太大了。嗯QX(n)Q对Q这?XQ必d _1、_2、_3 pd宏里面占据一个参数地位,q样才有那么一点点扩展性。考虑换成q样Q?/p>
#define REP_0(macro, n) macro(0)
#define REP_1(macro, n) REP_0(macro, n), macro(1)
#define REP_2(macro, n) REP_1(macro, n), macro(2)
#define REP_3(macro, n) REP_2(macro, n), macro(3)
#define REP_4(macro, n) REP_3(macro, n), macro(4)
#define REP_5(macro, n) REP_4(macro, n), macro(5)
#define REP_6(macro, n) REP_5(macro, n), macro(6)
#define REP_7(macro, n) REP_6(macro, n), macro(7)
#define REP_8(macro, n) REP_7(macro, n), macro(8)
#define REP_9(macro, n) REP_8(macro, n), macro(9)
#define REP(macro, n) REP_##n(macro, n)
然后Q?/p>
#define TYPENAME_LIST_PATTERN(n) T##n
#define TYPENAME_LIST(n) REP(TYPENAME_LIST_PATTERN, n)
q个 TYPENAME_LIST 是W合上文要求的宏。接下来如法炮制其余三个Q?/p>
#define TYPENAME_DECLARE_PATTERN(n) typename T##n
#define TYPENAME_DECLARE(n) REP(TYPENAME_DECLARE_PATTERN, n)
#define TYPENAME_VARIABLE_PATTERN(n) T##n v##n
#define TYPENAME_VARIABLE(n) REP(TYPENAME_VARIABLE_PATTERN, n)
#define VARIABLE_LIST_PATTERN(n) v##n
#define VARIABLE_LIST(n) REP(VARIABLE_LIST_PATTERN, n)
最后,我们?#define FUNCTION_IMPLEMENT(n) REP(BODY, n) 中还存在一点点问题。因?BODY 中会含有 TYPENAME_DECLARE 之类的宏的用,?TYPENAME_DECLARE 正是使用 REP 定义的。这涉及到宏的递归展开QC++预处理器的规则是Q遇到这L情况停止展开。比如,我们 定义 BODY(n) ?TYPENAME_DECLARE(n)Q于?FUNCTION_IMPLEMENT(2) 会被展成Q?/p>
REP(TYPENAME_DECLARE_PATTERN, 0), REP(TYPENAME_DECLARE_PATTERN, 1), REP(TYPENAME_DECLARE_PATTERN, 2)
上面?REP 不会被l展开了?/p>
为此Q一个不太聪明的办法是Q再定义一l?REP2。嗯Q是个办法,p么办吧。另外我们刚才的 REP pd没有分隔符作ؓ参数Q默认用逗号Q而最后一不的 FUNCTION_IMPLEMENT 的重复中是不能用逗号的。考虑提取出来作ؓ参数。最后我们的所需要的宏系l是Q?/p>
#define NIL
#define COMMA ,
#define REP_0(macro, splitter, n) macro(0)
#define REP_1(macro, splitter, n) REP_0(macro, splitter, n) splitter macro(1)
#define REP_2(macro, splitter, n) REP_1(macro, splitter, n) splitter macro(2)
#define REP_3(macro, splitter, n) REP_2(macro, splitter, n) splitter macro(3)
#define REP_4(macro, splitter, n) REP_3(macro, splitter, n) splitter macro(4)
#define REP_5(macro, splitter, n) REP_4(macro, splitter, n) splitter macro(5)
#define REP_6(macro, splitter, n) REP_5(macro, splitter, n) splitter macro(6)
#define REP_7(macro, splitter, n) REP_6(macro, splitter, n) splitter macro(7)
#define REP_8(macro, splitter, n) REP_7(macro, splitter, n) splitter macro(8)
#define REP_9(macro, splitter, n) REP_8(macro, splitter, n) splitter macro(9)
#define REP(macro, splitter, n) REP_##n(macro, splitter, n)
#define REP2_0(macro, splitter, n) macro(0)
#define REP2_1(macro, splitter, n) REP2_0(macro, splitter, n) splitter macro(1)
#define REP2_2(macro, splitter, n) REP2_1(macro, splitter, n) splitter macro(2)
#define REP2_3(macro, splitter, n) REP2_2(macro, splitter, n) splitter macro(3)
#define REP2_4(macro, splitter, n) REP2_3(macro, splitter, n) splitter macro(4)
#define REP2_5(macro, splitter, n) REP2_4(macro, splitter, n) splitter macro(5)
#define REP2_6(macro, splitter, n) REP2_5(macro, splitter, n) splitter macro(6)
#define REP2_7(macro, splitter, n) REP2_6(macro, splitter, n) splitter macro(7)
#define REP2_8(macro, splitter, n) REP2_7(macro, splitter, n) splitter macro(8)
#define REP2_9(macro, splitter, n) REP2_8(macro, splitter, n) splitter macro(9)
#define REP2(macro, splitter, n) REP2_##n(macro, splitter, n)
#define TYPENAME_DECLARE_PATTERN(n) typename T##n
#define TYPENAME_DECLARE(n) REP(TYPENAME_DECLARE_PATTERN, COMMA, n)
#define TYPENAME_LIST_PATTERN(n) T##n
#define TYPENAME_LIST(n) REP(TYPENAME_LIST_PATTERN, COMMA, n)
#define TYPENAME_VARIABLE_PATTERN(n) T##n v##n
#define TYPENAME_VARIABLE(n) REP(TYPENAME_VARIABLE_PATTERN, COMMA, n)
#define VARIABLE_LIST_PATTERN(n) v##n
#define VARIABLE_LIST(n) REP(VARIABLE_LIST_PATTERN, COMMA, n)
#define FUNCTION_IMPLEMENT(n) REP2(BODY, NIL, n)
最后,定义一?FUNCTION_IMPLEMENT(5)Q就可以支持?6 个参C。ؓ了支持更多参敎ͼ把上面的 REP 以及 REP2 pd多定义一点,比如?50Q那?FUNCTION_IMPLEMENT 的括号中可以填 50 以内的Q意数了。考虑到宏展开对编译速度的媄响,以及实际应用中函数参数的个数Q定?20 左右比较合适?/p>
到这里,我们的Function已经实现了预期目标。接下来我本来想说说 TypeList 的。可是现在发现没?TypeListQFunction 跑的通;有了 TypeListQFunction 也不能写的漂亮多,虽说那些重复部分有一定的减少。Loki ?Functor 的参数类型是一个返回值类型加上一?TypeListQ是qL接传?TypeList 的,不用由散的类型组合出一个TypeListQ但q是要从TypeList中萃取各个参数类型)Q因此用在他那里看上ȝ妙一点点。当ӞLoki 也在 Functor 外头包了一?FunctionQ以支持函数{作ؓ模版参数的用方式。有一点不改观的改观是,用了 TypeList 以后Q就不会再有 FunctionBase_1, FunctionBase_2 q样的玩意儿了,取而代之的是一个统一?FunctionBase 外加许多偏特化版本,Function* ?MemberFunction* 可以分别l一Z个,但是每一个里头都需要实?N ?Invoke。加上篇q关p,我想q里׃说这?TypeList 了?/p>
代码清单太长了,׃贴了Q有意者自然能凑v来。我目前?xlLib 中的最l实现见 xlFunction.h?/p>
关于宏,我不知道可以怎样改进。BOOST_PP_REPEAT 貌似可以调用自nQ不知道如何实现的,求指教。另?a href="http://www.shnenglu.com/vczh" target="_blank">@vczh貌似说“实C一门可以自己递归自己和内|列表处理的另一个宏”,求分享呀求分享?/p>
2010-01-18 补充Q将最外层 Function 的构造函C?const T & 直接改ؓ TQƈ且抛?FunctionTraitsQ函数实体类型将在传递过E中直接退化ؓ函数指针Q这样就能特化出正确?FunctionHandler。同时带来另一点媄响:如果传入 FunctorQ字面上多一ơ拷贝动作。抛开q一点微的性能来讲Q这比刚才的 FunctionTraints 要好得多了?/strong>
C++ ?Function 对象的实玎ͼ上)
C++ ?Function 对象的实玎ͼ下)
起因?a href="http://www.shnenglu.com/Streamlet/archive/2011/01/16/138609.html" target="_blank">上一?/a>已经说过了。现在让我们直接q入主题。本文的目标是,让以下代码能利跑v来:
int intfun0()
{
return 1;
}
struct _intfunctor0
{
int operator()()
{
return 2;
}
} intfunctor0;
struct Test
{
int intmem0()
{
return 3;
}
} test;
int main()
{
Function<int ()> f1(&intfun0);
Function<int ()> f1_(intfun0);
Function<int ()> f2(intfunctor0);
Function<int ()> f3(&test, &Test::intmem0);
f1();
f1_();
f2();
f3();
return 0;
}
除了上述例子中显C的Q还要支持有q回值的函数和没q回值的函数Q以及有0个?个?个、……、MAX 个参数的函数Q参数类型无限制。最后实现的 Function 对象仅仅可以执行好。(至于是否可拷贝、是否可判断相等 {问题,都是事Q本文暂不考虑。)最后,Bind 概念也不在本文讨围之内?/p>
对于q个问题Q我们一开始考虑的可能是怎样l一三种不同形式。有两个选择Q第一Q?C++ 的多态机Ӟ最后统一到基cL针的cdQ第二,允许cd部有冗余变量以及必要?FlagQ用于判断是哪种形式的函敎ͼ要如何执行。这LhQ第一U方案比W二U爽一炏V于是,最初想到的实现有可能是q样的:
先定义一个虚基类Q?/p>
template <typename R>
class FunctionBase0
{
public:
virtual R Invoke() = 0;
virtual ~FunctionBase0() {}
};
然后实现一个普通函?仿函数的版本Q?/p>
template <typename R, typename T>
class Function0 : public FunctionBase0<R>
{
public:
R Invoke()
{
return m_Fun();
}
public:
Function0(const T &fun)
: m_Fun(fun)
{
}
private:
T m_Fun;
};
q里需要说明的是,如果是普通函敎ͼT会被特化?R() 或?R (&)() 或?R(*)()Q取决于使用的时候传?fun q是传入 &fun。所以不必另外实现针?R(*)() 的版本。Loki Q姑且就以作品名UC Loki 的作者吧Q他那个真名实在是太长)在他的书中称之ؓ?font face="Consolas">做一个,送一?/font>”。不q对于他书中所说的Q我有一个疑惑。Loki 说传?funQ模版参?T 会被特化?R (&)()Q于是一切顺利。可是我在操作过E中发现 T 一直被特化?R ()Q于是上q?class 中的 m_Fun 被认为是成员函数而不是成员变量。不知道是ؓ什么,有知道者请不吝指教哈。因Z上原因,本文中我一直用 &fun 的Ş式对待普通函数?/p>
再实C个成员函数的版本Q?/p>
template <typename R, typename T>
class MemberFunction0 : public FunctionBase0<R>
{
public:
R Invoke()
{
return (m_pObj->*m_pMemFun)();
}
public:
MemberFunction0(T *pObj, R (T::*pMemFun)())
: m_pObj(pObj), m_pMemFun(pMemFun)
{
}
private:
R (T::*m_pMemFun)();
T *m_pObj;
};
最后是一个包装类。如果你可以接受 Function<int> 表示 int()Q?Function<int, int> 表示 int (int)Q…,那么q里没有多少技巧可a。boost 的那?function 使用的是函数{作ؓ模版参数Q即 Function<int()>QFunction<int (int)> {Ş式。如果不太研I语法,可能会像我一P一开始会对尖括号里的 int (int) 之类的玩意儿不太熟悉Q觉得很牛逹{可是了解了以后Q不q是个函数类型而已Q没什么大不了的。Loki ?Functor 的用方式是 Functor<int, TYPELIST_0()>QFunctor<int, TYPELIST_1(int)>。其中第一个模版参数始l是q回|W二个模版参数是参数cd列表QLoki 使用了他创造的玩意?TypeList 使得所有函数参数只占一个坑Q这在等下的支持多参数的扩展中能够带来一些美观。我比较喜欢 boost 的用方式,让用者直接以语言规定的Ş式填入函数签名,而不是一些额外的U定Q“第一个模版参数表C回值”,“第二个到最后的模版参数表示参数”,“第二个模版参数?TypeList 形式表示函数参数”等Q?/p>
Z辑ֈq个目标Q我们要玩一些偏特化技巧。关于偏特化Q我一直以来的肤浅认识都是错误的。我原以为,对于模版c:
template <typename T0, typename T1>
class Foo;
我如果特化其中一个参?T1Q?/p>template <typename T0>
class Foo<T0, int>
{
}
我以为只有这h叫偏特化Q以为偏特化的过EL减少模版参数的。而实际上Q只要用某个/些类型占据原始模版参数的位置Q就可以了。比如,对于上述 FooQ我可以特化一?class<T0, std::map<U0, U1>>Q消M?T1Q而新?U0、U1Q?/p>template <typename T0, typename U0, typename U1>
class Foo<T0, std::map<U0, U1>>
{
}
原来 T1 的位|被 std::map<U0, U1> 占据了,q也是偏特化。当然最后的模版参数数量也可以不变,如:
template <typename T0, typename U>以及
template <typename T0, typename U>其中后者是实现cd萃取的主要方式。只要特化以后,q个cM然带有至一个模版参敎ͼ是偏特化。如果最后生了 template<> 的Ş式,那就是完全特化?/p>
回到我们刚才的主题,我们要提供给用户的是q样一个类Q?/p>
template <typename Signature>
class Function;
其中参数 Signature 会被实际的函数类型所特化。但是我们只知道整体的一?Signature q没有用Q我们必ȝ道被分解开来的q回值类型、参数类型。于是,引入一个偏特化版本Q?/p>
template <typename R>
class Function<R ()>
q里使用 R () 特化原始?SignatureQ引入一个新的参?R。于是返回值类?R p萃取出来了。实现如下:
template <typename R>
class Function<R ()>
{
public:
template <typename T>
Function(const T &fun)
: m_pFunBase(new Function0<R, T>(fun))
{
}
template <typename T>
Function(T *pObj, R (T::*pMemFun)())
: m_pFunBase(new MemberFunction0<R, T>(pObj, pMemFun))
{
}
~Function()
{
delete m_pFunBase;
}
R operator ()()
{
return m_pFunBase->Invoke();
}
private:
FunctionBase0<R> *m_pFunBase;
};
如果对上面说的“普通函数的使用方式必须是函数指针而不是函数本w”耿耿于怀Q可以再引入一个的构造函敎ͼ
typedef R (FunctionType)();
Function(const FunctionType &fun)
: m_pFunBase(new Function0<R, FunctionType &>(fun))
{
}
q里 FunctionType ?R(&)() cdQ强制用它来特?Function0 中的 T。该构造函数在重蝲册中会取得优先权从而普通函数本w的传入成ؓ可能。不q,以函数本wŞ式传入的普通函C丧失一些特性,比如 Function<int()> 只能接受 int() cd的普通函数而不能接?char () 型的普通函敎ͼ因ؓq种情况下不会走我们刚才新定义的构造函数?/p>
q有一U做法,是针对全局函数Q强制特化出模版参数为其引用cd的类。定义如下元函数Q?/p>
template <typename Signature>
struct FunctionTraits
{
typedef Signature ParamType;
};
template <typename RetType>
struct FunctionTraits<RetType ()>
{
typedef RetType (&ParamType)();
};
然后构造函数改为:
template <typename T>用以上方法,所有的Ҏ都不会丢失?/p>
到这儿,我们?Function 已经可以试牛刀了:
Function<int ()> f1(&intfun0);
f1();
f1_();
f2();
f3();
上面q段代码已经能够正常q行了?/p>
来,l箋做一个,送一?/strong>。下面的代码居然也能跑(voidfun0、voidfunctor0、Test::voidmem0cMint版本定义Q:
Function<void ()> f4(&voidfun0);f4();
f4_();
f5();
f6();
q说明了Q在c里面写一个返回gؓ该类型的函数Qƈ在里面写?return XXX; 然后?void 为模版参C入该模版c,是符合语法的。验证一下:
template <typename T>
class Foo
{
public:
T Bar()
{
printf("%s invoked\n", __FUNCTION__);
return T();
}
};
int main()
{
Foo<void> f1;
f1.Bar();
Foo<int> f2;
int i = f2.Bar();
return 0;
}
q行l果Q?/p>
Foo<void>::Bar invoked
Foo<int>::Bar invoked
到此为止Q我们已l实C 0 个参数的函数支持Q也?R () cd的所有函数的支持。接下来q要实现对具?1 个? 个? 个直至Q意有限个参数的函数支持。也许您也发CQ接下来的工作可以是体力z,我们可以照葫芦画瓢,搞出一?FunctionBaseN、FunctionN、MemberFunctionNQƈ在最后的 Function 中再实现 N 个偏特化版本。是Q不错,大致上原理就是这栗限于篇q,我想暂时写到q里Q下将l箋谈谈宏、TypeListQ以及怎样花点力气实现其?N 个版本。最l达到的效果是,只要改一个宏定义Q就可以提高参数上限?/p>
在本文所涉及的内容中Q我比较U结的是Q可否在不用多态机制的情况下达到比较优雅的形式l一Q?/p>
Ƣ迎讨论?/p>