posts - 16,  comments - 34,  trackbacks - 0
共10页: First 2 3 4 5 6 7 8 9 10 
re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-17 20:54
@visualfc
Qt Creator我知道,Qt Creator的cplusplus是指的什么?
Qt Creator的自动完成功能吗?

我看过你的一些作品,果然对这方面的东西了解很多~_~


@陈梓瀚(vczh)
其实我的需求上面也有说…… 就是将代码染染色……
不然直接在blog上贴代码太丑陋了……
re: XL Library Preview,诚征指点 OwnWaterloo 2009-11-17 20:21
@溪流
某人犯傻去继承string,那是他的责任。
没有必要为了避免他的错误,让所有人让步,承受虚指针的开销。

反向迭代器,我猜的也是这么回时。
不过具体没看。
去看看代码吧,应该不多。
可能会有一些细节,大致想的时候会被忽略。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 22:01
@空明流转
好东西啊,好东西,看得我心花怒放~_~
再次感谢~_~


顺便也感谢一下cexer。
果然是【阅尽MFC,WTL,SmartWin++,win32gui,jlib2 ,VCF ;看尽天下 GUI 的感觉。】

果然只看示例和书不写代码是不行的。就书上的示例代码,我还领会不了其精髓……
其实也不是我不想写…… 我写过一些……
然后就没机会写了……

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 21:47
@空明流转
我靠!还有这种工具!
太感谢了~_~

其实我也不做什么的啦……
主要是写文档(设计文档、博客)的时候染染色啦……
re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 21:27
@陈梓瀚(vczh)
【子类不需要非得实现什么,纯粹是模拟非纯虚函数。因为父类必须提供所有可以覆盖的界面的空实现,才可以让实现控件的人,只写他们关心的东西。】

上面已经说明了:
"父类提供所有可以覆盖的界面的空实现",或者说默认实现,并让实现控件的人,只写他们关心的东西 —— 这不需要ATL-style式的继承,普通继承就可以了。

只有在"向子类要求一些莫须有的东西"时,才需要依靠template。



【我重写VL++的目的就是想提供一个用来分析字符串(速度不用爆快,但肯定要好用)的库。虽然不一定会实现一个C++的语法分析器(实际上我可以告诉你我没法产生精确的语法树,如果不做语义分析我肯定不知道a<b,c>d;究竟是什么东西),但上下文无关着色什么的肯定是可以轻松实现的。】
编译原理那些东西几乎忘光……
我觉得为编辑器着色有一个相对于编译器来说很麻烦的地方……

编译器可以要求源代码必须是合乎语法的,否则就报错。
编辑器的着色分析就不能这么暴力了,它必须能够接受半完整的源代码,并且尽可能从中榨取出信息……

所以visual assistant还是很强大的……

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 21:08
@cexer
有点感觉了。

ATL-style继承,一方面有点类类似concepts或者说ducking type的使用。
template<class D>
class B
{
        void f()
        {
                D* d = static_cast<B*>(this);
                d->must_have_this_function(); // 必须有某个成员函数
                d->must_have_this_variable;   // 必须有某个成员变量
                typedef typename D::must_have_this_type type;
                // 必须有某个嵌套类型
        }
};

B可以对D提出一些要求,D必须满足这些要求。
对这些要求,B可以不必提供"默认实现"。
这2点就是和普通继承很不相同的地方了。


同时,ATL并没有通过"组合"来复用库代码,而是通过"共有实现继承"来复用库的代码。
使得编写的代码会少一些。 就像那个著名的例子:

class person : public public nose, public mouth {};

person p;
p.eat(); mouth::eat
p.smell(); nose::smell


实际上呢,更"学术气息"一些的方案是:
class person
{
        nose n_;
        mouth m_;
        eye e_[2]; // left and right
public:
        // forwarding functions
        void eat() { m_.eat(); }
        void smell() { n_.smell(); }
};


但是在界面开发中……  这些转发函数会写死人的……
所以,就产生了ATL-style继承 —— concepts(ducking type) + 实现继承的结合。


只是为ATL-style继承举例的人的例子没有选好,无法充分说明它的优点。
我只粗看过《mfc程序员的wtl编程指南》还是什么的。
里面就是一个"没有体现出base对derived的需求",可以用普通继承改写的例子。


这样理解如何?


re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 20:37
@cexer
【你试试不用虚函数,不用ATL的这种编译时的多态手法,在不知道子类为何物的情况下,在基类当中调用子类的方法试试? 】

应该是不行的。

只有template这种变态代码生成器才可以:
template<typename T>
void f(T v)
{
v.f(); // 让你先这么着了,等实例化的时候再做检查……
}

如果不是模板,父类是不能调用子类函数的。

那么,ATL-style继承的作用就是:实现静态的"template method"?

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 20:29
@cexer
你说的是这种情况么?

non-template

class B
{
        virtual void f_do() = 0;
        void f()
        {
                ... // 有一些其他工作,不仅仅是转发
                f_do();
                ...
        }
};

B可以自己不实现f_do,并且具有这么一个限制:如果派生类不实现f_do,派生类将继续无法实例化。


ATL-style继承,提供"类似"的含义。
template<class D>
class B
{
        void f()
        {
                ... // 有一些其他工作,不仅仅是转发
                static_cast<D*>(this)->f_do();
                ...
        }
};

class D : public B<D>
{
        void f_do() {  }
}

如果不实现f_do,D就会产生编译错误。
类似于如果上面那种D如果不定义f_do就无法实例化。



普通继承无法提供这种限制语意:
class B
{
        void f()
        {
                ... // 有一些其他工作,不仅仅是转发
                f_do();
                ...
        }
        // void f_do();
        // 如果提供,派生类不不覆盖也能实例化,也不会产生编译错误。
        // 无法达到限制"派生类必须提供"的目的

        // 如果不提供,B就会产生链接错误。
};

是这样吗?

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 20:13
@陈梓瀚(vczh)
【header-only已经不错了,我现在最烦一个类要将界面重复在两个文件里面。】

头文件不行吗?为什么会这样?


话说我想尝试自己实现BOOST_ITERATE(只用宏复制代码)的方法,最后研究了好久,发现用C的宏是不对的,应该自己制造代码生成器……C的宏缺乏语法上的可维护性

我也觉得宏很不可靠……
有时候完全可以利用其他一些很方便的语言,来产生C/C++源代码。
凭借C/C++自身机制来进行元编程,有时候会很…… 很麻烦……

比如boost.pp,它用来产生模板类型参数列表的这个功能,其实也可以写一些其他语言的代码,用来生成C++源代码……

可读性应该会好很多……

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 20:09
@陈梓瀚(vczh)
【其实我对语法最感兴趣……因为我做编译器……】
能否做一个,呃,怎么说呢……

visual assistant你肯定用的吧?类似的还有:
msvc,gcc :
synedit : http://synedit.sourceforge.net/
scintilla : http://www.scintilla.org/
还有一些C/C++源代码转换到html、pdf等格式的工具。

它们肯定都能分析C/C++源代码。
但是源代码分析后的结果,它们自产自销了,没有暴露出来。

你能否做做这方面的工作呢? 一个C/C++(或者其他语言)的源代码分析器,将分析的结果暴露出来。
方便其他用途使用,比如代码染色。

这样,很多编辑器、网页、甚至是pdf、doc都可以得益于你的工作。
而不是每个工具都自己实现一套(有的功能薄弱得……比如网页上的染色,基本就只能认识语言的关键字而已),然后自产自销。


你觉得这个提议怎样?

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 18:51
@陈梓瀚(vczh)
如何在属性实现状态变化控制的技术嘛,没有说过命名……看来我们是互相误解了啊…… 

那你也说说你的想法嘛……
比如那个“效率问题”应该怎么理解?

有人说可以拿C/C++程序员作巴浦洛夫实验,看提到"效率"2字时他们是否会流口水……

我没有流口水…… 但是我很好奇……

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 18:39
@cexer
管饭的人呢……

这问题反复困扰我很久了…… 一直没得到解答…… 于是尘封了……
用ATL/WTL的人很少,问都找不到人问……
今天终于找到管饭的了, 一定得追问到底~_~

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 18:20
@陈梓瀚(vczh)
【如果你的库不用dll+lib+h提供的话,请尽情使用模板】……

如果ATL-style继承的好处就是为了header-only。

class Base
{
        void OnDraw() { /* inline */ }
};

或者:
base.h
class Base
{
        void OnDraw();
};


base.inl
#include "base.h"

inline void Base::OnDraw() {}


也可以是header-only ……


那么,ATL-style继承,还有什么好处么……
一开始,我也觉得很神奇…… 后来越想越不是那么回事…… 然后一直没想通……

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 18:16
@陈梓瀚(vczh)
@cexer

我上面也说了, 这和普通的继承实现的功能是完全一样的。
依然没有虚函数开销,依然有一点点"多态"。

用vczh的例子来说:
class Base
{
        void OnDraw() { 空 }
};

class Derived1 : public Base
{
        void OnDraw() { 我有新OnDraw }
};

class Derived2 : public Base
{
// 我用默认Draw
};

不也一样吗? 除了Base现在是一个类,而不是类模板。
所以,我现在唯一想出的ATL-style继承的优势就是:header-only。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 18:06
@陈梓瀚(vczh)
属性变化就是状态变化嘛,你要监听一个状态的变化,还要控制一个状态变化的权限,用属性+事件的组合是很常见的。也就是说,一个属性都要自动提供Changing和Changed两个事件。尝试把它做得高效,就是我之前的需求了。 

这才是属性的本质概念,跟语法什么的关系并不大。

我觉得你又开始继续扯远了……
本来不是在讨论“proxy的实现技术”“property和命名函数”么……

anyway,换个话题也好。
不过我真没看明白你的需求是怎样的……

这个需求是否高效,与“是否使用union”有关系吗?
与“property vs 命名函数”有关系吗?
是一个新话题吧?


能详细说说你的需求么?
re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 18:02
@cexer
ATL-style继承,提供的语意是:
基类提供默认实现,派生类继并覆盖自己需要的函数。
是这样吗?
除了这个,还有其他功能吗?

但这个功能,不正好就是非ATL-style继承 —— 普通继承提供的语意吗……
为什么要绕这么大一个圈子?
能得到什么好处? header-only?

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 17:57
@cexer
绝没问题,欢迎有自己思考的同志来此版聊,如果能切紧GUI框架的主题就感谢了哈,你们的讨论能使我的博文增色不少。聊完不要走,此处管饭。

别管饭了……  正好有一个悬了很久的关于ATL/WTL的疑问,你管管吧~_~

是这样的,ATL/WTL中提到一种atl-style继承。
template<typename D>
class B
{
        void f1() { static_cast<D*>(this)->f1_do(); }
        void f2() { static_cast<D*>(this)->f2_do(); }
        void f1_do() { printf("B\n"); }
        void f2_do() { printf("B\n"); }
        
};

class D : public B<D>
{
        void f1_do() { printf("D\n"); }
};

D d;
d.f1();
D::f1不存在 -> 调用B::f1 -> 转型为D* -> D覆盖了f1_do -> 调用D::f1_do ->输出"D"
d.f2();
D::f2不存在 -> 调用B::f1 -> 转型为D* -> D::f2_do不存在 -> 调用B::f1_do->输出"B"

是这样吗?


然后,问题就来了 …… 这个样子,除了将代码写得更复杂以外,相比下面的写法,有什么区别么??

class B
{
        void f1() { printf("B\n"); }
        void f2() { printf("B\n"); }
};

class D : public B
{
        void f1() { printf("D\n"); }
};

D d;
d.f1(); "D"
d.f2(); "B"


re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 17:43
1:先做的事情写在前面,后做的事情写在后面,要花时间看清楚。 
2:不要造成括号的嵌套,难以维护。 
举个例子,挑选列表里面所有偶数的数字然后平方,如果你写成square(even(a_list))就不满足1和2。 
【注意,上面两点你看过就算了,我不讨论好跟不好,因为跟主题没关系】

不能算了……
text.onchange_set( handler1 ).onchange_add( handler2 ).onchange_add( handler3 );
满足上面的2点吗?


对于你的点的例子,我倾向于CalculateRadius(your_point)。你不能否认人们总是下意识地认为获取属性比调用函数的效率要高,所以那些真的做了事情,又不改变状态,又只读的属性变成函数比较好。
点的例子是因为你说point不需要行为而举出的反例。

我也倾向于:
point
{
        double x() const;
        void x(double xx);
        double r() const;
        void r(double rr);
        // ..
};

我也没有假设get_r( p ); 和 p.r() 或者p.r到底何种效率更高。
这取决与内部表达是直角坐标还是极坐标。

这些都不是重点。重点在于:
1. r绝对不可以是裸域 —— 它需要维护不变式
2. 如果你打算将x、y、r、a由函数变为属性,4个指针的开销你觉得是否合适?

btw:这4个属性都可以是读写的,并不存在x、y读写,r、a只读一说。


不过我还是想跟你讨论“在一个包含请求回调doing和响应回调one(+=也好,.Add也好)的属性,怎么避免开销”而不是在这里要不要用只读的属性。
我没看明白,能否再详细一点?

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 17:23
【我跟OwnWaterloo就借着你的地盘版聊了哈】

要不我们开一个论坛吧?google groups或者群?
群的聊天资料可能不容易搜出来……

toplangauge越来越水了……

把cppblog上的人都招集起来?

很高兴和你们讨论问题~_~
如果我语气有过激之处,请不吝指出,或者多多包涵一下。

有人响应没……?

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 17:19
【我并没有反对你优化,我只是说“你觉得在一个【上百个】属性的Form里面,你打算怎么处理属性和事件之间的关系”。正好本篇是谈GUI,因此举这么个例子我觉得挺合适的。那我就这么问吧,我以为你是知道我在说什么的:在一个包含请求回调doing和响应回调one(+=也好,.Add也好)的属性,怎么避免开销?】

首先,上百个属性是否可以改为上百个×2的getter和setter?
setter不一定难用,setter还可以链式表达呢。

对比:
1. w.p1(v1).p2(v2).p3(v3) ...
2. ( (w.p1 = v1).p2 = v2 ).p3 = v3 ...
3.
w.p1 = v1;
w.p2 = v2;
w.p3 = v3;


其次,如果真要模拟property。
我真的没看明白你说的那个需求是什么…… 囧


re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 17:11
【行为指的是受改变的时候发生的事情,而不是你能从一个数据里面计算出什么东西。坐标转换是映射,点在任何情况下都不可能因为你设置了一个什么坐标而做了一些什么事情,因此没有行为。当然cache不同坐标系下的结果例外,这不是重点。】

可能我们对“行为”这个词有不同的理解。 那我们不用这个词了。

对刚才说的点的例子。半径绝对不能直接读写对吧?
所以必须将实现隐藏起来(实现可能是以直角坐标存储), 通过setter/getter去操作,以维护点的不变式 —— 半径不可能小于0。

我说的就是这个意思。

如果你需要可按极坐标操作的点,就不能直接暴露数据,必须使用setter/getter或者property。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 17:06
【【窄化】不是问题。GUI是一个庞大的东西,一旦需要了,那就有做的必要,就算除了GUI以外的所有系统都不能从这个property机制上获得好处,也不是不搞的理由。而且让一个例子举现在一个范围里面可以让我们都少想很多东西。】

就算这种O(n)到O(1)的优化因为它太复杂而永远用不上,【也不是不搞的理由】。
我这么说对吗?

讨论如何优化就是讨论如何优化。
没有必要将这种讨论限制到某个范围内,然后说在这个范围内不需要这种优化。

你不能预见所有场合都不需要这种优化。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 17:01
【至于其他的例子,你可以参考WPF强大的布局功能是怎么【under control】的,用传统的方法根本没法让程序变得清晰。】

我不懂wpf。
不通过属性,而是命名方法,可以做到让程序清晰么?

如果只是从:
textBox.Text.OnChanging+=a_functor_that_want_to_check_the_input;

变化到:
textBox.Text.OnChanging.add( a_functor_that_want_to_check_the_input );
textBox.Text.OnChangingSet( a_functor_that_want_to_check_the_input );
textBox.Text.OnChangingAdd( a_functor_that_want_to_check_the_input );

我觉得依然很清晰。
也许前者对C#转到C++的程序员来说,前者会很亲切。
但后者才是大部分C++程序员的每天都在使用形式。

你不需要给你的用户培训“C++中如何模拟property”这种细节,他就可以使用你的api。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 16:56
【其实举过了嘛,textBox.Text.OnChanging+=a_functor_that_want_to_check_the_input;你觉得在一个【上百个】属性的Form(你不要告诉我一个窗口的状态很少)里面,你打算怎么处理属性和事件之间的关系,我一直都在强调这一点,而不是语法上的问题,operator=其实也是个例子,我没有强调说要不要有个名字之类的东西。 】

textBox.Text.OnChanging+=a_functor_that_want_to_check_the_input;
你觉得改成这样,可接受不?
textBox.Text.OnChanging.add( a_functor_that_want_to_check_the_input );
textBox.Text.OnChangingSet( a_functor_that_want_to_check_the_input );
textBox.Text.OnChangingAdd( a_functor_that_want_to_check_the_input );

C++就是C++,没有必要模拟.net那一套。


【另外,不要觉得什么掉价不掉价的,技术没有贵贱之分,不要搞三六九等。话说我自己在搞语言设计和实现。从来跟这些property什么的关系不大,这估计是职业病。】
因为我觉得通过union来达到property的额外空间与property数量无关这个技巧可能是新奇的。我没有在其他地方看到过,也是我自己想出来的。

而proxy的那些技巧,我已经看太多,说不定大家都知道,我又何必多次一举?
而且很多都不是我独立想出来的。
所以我觉得说出来很掉价……

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 16:42
"当然这要看情况了。在我看来,每一个控件都要实现可监听的属性,这些属性用事件暴露出来。你愿意设计完属性之后从头设计一次事件的命名和参数什么的好,还是愿意把它们绑定到属性里面去?这样的话开发控件也不需要考虑提供的事件完整不完整的问题了。因为控件的状态都在属性里面,属性变更就是状态变更,也就都有事件了。这样做的话设计出来的控件就不会有winapi那个消息(我们都知道很难清洗化)的影子了,会有一个十分漂亮的设计出来。"

消除winapi的影子,一定需要属性吗?命名函数不可以吗?
如果语言本身提供属性,那使用两者之一都没什么问题。
如果语言本身不提供,为何在可以使用命名函数的地方,非要去使用属性???



"一般winapi封装出来的东西都是在PC上跑的,你会在意一个textbox占了1k还是1.1k内存吗?在这里多一个指针少一个指针根本不是问题。 "
在gui上,你的说法是正确的。你甚至可以使用boost::any + boost::tuple来得到一个类似python的开发方式。

我讨论的是"proxy得到object", 请不要将讨论窄化gui上。
作为一个库(非gui库)的提供者,效率不是最主要的,但无端的损害(大量损害)就是不可接受的了。
假设某天你真的需要将proxy应用到critical的场景,你就会后悔当初没有去考虑这个问题了。


"特别是对于point,那就是个变量好了,point没有行为,所以不需要属性。"
需要的。我举个例子:
point
{
        property double x { get;set; }
        property double y { get;set; }
        property double radius { get; set; }
        property double seta { get; set; }
};

客户可以用直角坐标或者极坐标去操作这个点。
点的内部表示客户无须关心。
这样的点,就需要行为。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 16:32
"模拟属性一点都不无聊,因为一个可以监听和撤销属性变化的系统非常容易扩展,总比你一个类都来一个listener或者就像api一样所有的消息的格式都很混乱的好。特别对于开发GUI的库。当然这就不仅仅是如何响应operator=的问题了。允许阻止变化的listener总是有开销的,所以你无法杜绝它。这也是我第一次举例子的时候就说出来的一个可以认为是“需求”的东西。"

模拟属性就是很无聊的工作。
你试着回答一个问题:”这里一定需要operator=吗? 可以使用命名函数吗?
如果回答是“是” ,那这里就宁可采用命名函数,而不是属性。
绝大部分回答都应该是“是”。所以这根本不算一个需求。


从我的直觉上来说,模拟属性使用的价值并不大。没有多少场合非属性不可
如果你可以想到一个“不是”的例子,请列举出来。
也许我的直觉是错的。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 16:32
"模拟属性一点都不无聊,因为一个可以监听和撤销属性变化的系统非常容易扩展,总比你一个类都来一个listener或者就像api一样所有的消息的格式都很混乱的好。特别对于开发GUI的库。当然这就不仅仅是如何响应operator=的问题了。允许阻止变化的listener总是有开销的,所以你无法杜绝它。这也是我第一次举例子的时候就说出来的一个可以认为是“需求”的东西。"

模拟属性就是很无聊的工作。
你试着回答一个问题:”这里一定需要operator=吗? 可以使用命名函数吗?
如果回答是“是” ,那这里就宁可采用命名函数,而不是属性。
绝大部分回答都应该是“是”。所以这根本不算一个需求。


从我的直觉上来说,模拟属性使用的价值并不大。没有多少场合非属性不可
如果你可以想到一个“不是”的例子,请列举出来。
也许我的直觉是错的。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 16:27
"举个简单的例子,总线形式的开发会让string有一个c_str(),而不是让string消失。我不知道你知不知道解决方案是什么样子的,但是举个例子出来总是要告诉别人说,“哦,这个东西可以这么这么做,只是我懒的写完整”而不是没说完。"

我相信看的人能明白我代码中的重点在那里。
我不明白的是你为何看不明白,即使在我说了之后。

我再说一下吧,如果要让我说"这个property"还可以这样玩、又可以那样玩,我会觉得很掉价……
一方面因为我现在本来就不喜欢过分玩弄语言特性。
另一方面,这些技术实在是说得太多……


如果一定要把所有细节都展现出来,不知道评论的字数会不会够。
而且,展现到什么程度才可以停止?

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 16:15
@陈梓瀚(vczh) "在关心程序的质量的同时,也要同时关心一下如何将广大程序猿在C++的水深火热之中解放出来" 我展示的,只是"proxy得到object"的技术。 你可以继续展示proxy得到object之后的"解救广大C++程序员于水深火热之中"的其他技术。 但说实话,我不觉得那些技术真的能救人于水火
真正救人于水火的,不是玩弄高级技巧,而是设计清晰的接口。
还是上面的例子: C c; C2 c2; c.f( c2.i ); 不能这样做,只会让人感到困扰。 如果i不是属性,而是成员: c.f( c2.i() ); 不行是很容易理解事情 —— 临时对象不能绑定到非const引用上。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 16:07
@陈梓瀚(vczh)
"不过用了union,而跟你刚才那样一个属性就要定义一个类,而不是一种类型的属性定义一个类(或者干脆就只有少数几个属性的类然后到处使用——这个要求比较高了一点),我觉得比多一个指针更加不能接受。运行时代价跟开发代价都是需要一并考虑的。"

先说运行代价和开发代价。
C程序员可能会因为效率,不使用qsort。 他们都是傻子吗?

"实现属性占用的额外空间大小与属性的数量无关",这难道真的不值得你为其多花点功夫吗?
这是O(n)到O(1)的优化!是不值得的?!


而且我比较怀疑你所说的"一种类型的属性定义一个类"是一种[color=red]侵入式[/color]的实现方式。


需要实现属性的类本来就不应该很多。
我也想如何做得更范化一些, 但目前成效不大。

就像cexer说的"高效的,不会制造麻烦的东西,也是从不高效的,会制造麻烦的东西进化来的。"
如果一开始就采用指针保存,那是一辈子就没办法优化了。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 15:59
@陈梓瀚(vczh)
"如果别人解决了一个带多余指针的好用的属性,你解决了一个不带多余指针的不好用的属性,那都是没意义的。"

我那个是没有意义的?
我那个不可以继续实现你所说的那些花哨的用法吗?(为什么说花哨,我下面会说明)

我已经说过很多次了,"通过proxy得到被proxy的对象"是实现proxy的前提。
必须先满足这个前提才能继续实现proxy的其他技巧。

用union会影响实现其他技巧吗? 依然可以。
难道我要把所有我会的技巧都展现一下吗?
我不喜欢这样[color=red]显摆[/color]。
或者说,我也喜欢显摆;但[b]我不喜欢显摆[color=red]众人皆会[/color]的技巧[/b]。
分享一些别人明显了解的技术,只会[b][color=red]涨[/color]别人眼睛[/b]。


我只想展示如何高效实现proxy所需前提这一技巧,如是而已。



btw:为什么我说你那些是花哨的技巧。

首先引入一个概念:"总线式的设计"。

假设你需要实现一个函数f,f需要一个字符串输入(暂不考虑wchar_t)。
如何设计这个输入参数???

1. f(const char* c_style_string);
2. f(const char* first,ptrdiff_t length);

两者取其一,可能1用得更多一些。

然后,其他所有的String,都向这个"总线" —— const char*靠拢。
比如std::basic_string要提供c_str(), CString要提供operator const char*()。

为什么要这么做?
1. 避免API的暴增。 直连线是平方增长,而总线是线性增长。
2. 有些时候,必须这样
对fopen(const char*)你可以继续直连线:
fopen(const std::string& );
fopen(const CString& );

对一个类:
class C {
open(const char* );
};

C不是你写的,你不可能为C加入一个直连线。
你只能绕弯:
open( C& o, const std::string& );
open( C& o, const CStirng& );


回到你说的ref<T>。

假设有一个函数:
f(int& );
你可以为它直连线。


假设有一个类,依然不是你写的,它只接受int &
class C
{
f(int & );
}

C2有一个属性i。
class C2
{
property int i;
}

你要怎么给你的用户解释:
C c;
C2 c2;
c.f( c2.i ); 是行不通的,必须
f( c, c2.i ); ???

你能将所有api的汇合点都直连线吗? 你做不到。


本来模拟属性就是一个很无聊的事情了。
你告诉用户,这就是一个C#中的 get; set; 就ok了,别想着要其他怎么用。
C#中可以 &o.p吗???



"运行时代价跟开发代价都是需要一并考虑的。"
你看你是否自相矛盾了。
将线性开发代价的总线形式,转化为平方开发代价的直连线形式。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 14:02
@cexer
我再想想如何做得更范化一些。
减少需要编写property的人的重复劳动。

但是遇到了困难……

手工写union base, union member没什么问题。
要将其做成模板…… 会遇到先有鸡还是先有蛋的问题……

比如:
proxy< ..., getter , ... >

实例化一个property:
proxy< ..., &date::get_second, ... >; 这里需要date的定义?

而date的定义无论是union base,union member又需要先实例化proxy< ... &date::get_second, ... >;……
很囧……


手工写没问题是因为:
struct second_proxy {
这里仅仅声明其布局就可以了
};

然后定义date类

然后实现proxy::op ...
这里才需要引用到date::&getter


我再想想……


re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 13:50
@陈梓瀚(vczh)
1.
如果一个property需要消耗一个指针的代价,我认为实现得再好用都是不可接受的…… 高得有点离谱。
很容易一个类的property的消耗比类本身还要高。
[data of instance]
[pointer]
[pointer]
[pointer]
[pointer]
....
而且所有pointer都指向同一个地方……

而且上面也提到,真正需要模拟出property的,是类似于point、vector、matrix这种类。
p.x比p.x()更自然。
让这种类附加额外数据,也是让人很难接受的。


2.
是否好用,你可以另外开文章讨论。
只是你提到的那些使得proxy好用的技术,都提不起我的兴趣。
理由很简单 —— 这种文章太多,写出来都不好意思和人打招呼。

我关心的仅仅是proxy如何高效得到被proxy对象 —— 实现proxy的前提。

我以前见过的,包括我自己想到的,都是上面one property one pointer的方案。imp cpp中提到的让我确实有眼前一亮的感觉。
而 op T(), op= (T), ref T op + 这些技术,只让我觉得涨眼睛 —— 看太多,早腻歪了。


如果你的重点依然在proxy如何更仿真上,可能很难提起我的兴趣。
如果你有兴趣思考proxy如何得到被proxy的object上,或者有一些方案,想法,欢迎分享出来~_~



"当且仅当好用,才有继续考虑有没有消耗的必要。一个高效的、会制造麻烦的解决方案一般是不被接受的。"
这句话绝对说得过于绝对了。
如果你用的是C++、而不是C#,效率绝对不是可以不挂在心上的事情。
为什么你要用C++,而不是用编程更方便,语法糖更多的C#?好好想想吧。

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 13:19
@陈梓瀚(vczh)
还是用代码说话吧……

上面的date的代码,重点在于这2行:
union { /* ... */ };

哦,还有一行在我的测试代码中,并没有抄出来:
typedef int assume[sizeof(date)==sizeof(time_t)?1:-1];
这2行才是我想表达的重点 —— 没有额外的空间消耗,实现property。


写上面那些代码,为的是展示上面那个重点,而不是下面这种烂大街的:
operator = (int);
operator int() const;
这些技术早就被人讨论得七七八八了,相当无聊……


这一点不知道你看出来了没?
re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 13:12
@陈梓瀚(vczh)
我在上面的评论也说了,在C++中没有绝对必要,还是不要模拟这个东西了。
没有property,生活一样可以继续。


至于 o.p1 + p.p2, &p.p1, 等模拟,并不是重点。
proxy本来就和被proxy的对象有所区别。
视你需要模拟的程度来决定编程的复杂度,或者发现某种特性无法实现。

proxy的重点在于如何高效的在proxy中得到被proxy对象。
实现了这个功能点,才是其他op=,op T(), op +, ref proxy的前提 —— 这些技术基本属于滥大街了 —— 去看vector<bool> —— 所以我对这些没什么兴趣。

之所以重新开始研究这个技巧,是因为Imperfect C++中提到了一种代价很低的方案。
我觉得这个有点意思,所以继续深入了一下。
re: SFINEA in C++ OwnWaterloo 2009-11-16 03:58
@唐风
我试试 …… 谢谢~_~

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-16 02:48
关于隐藏time_成员……
 
1.
class C
{
public:
        union
        {
                proxy1 property1;
                proxy2 property2;
                proxy3 property3;
                // private: // msvc和gcc这里是不能加入private的(具体要查标准)
        }
};
 
 
2.
class C
{
public:
        union
        {
                proxy1 property1;
                proxy2 property2;
                proxy3 property3;
        }
private:
        struct impl impl_; // 一旦不放在union中
        // properties就可能会被填充
};
 
 
仔细选择union的位置,可能会起到减少总大小的效果,比如:
class C1 {
        char c_;
public:
        union { /* properties */ };
private:
        int i_;
};
class C2 {
        int i_;
        char c_;
public:
        union { /* properties */ };
};
 
因为char c_;的附近本来就会被填充,properties刚好占据那个位置。
 
 
如果这样,就可能会很糟糕:
class C3 {
        char c_;
        int i_;
public:
        union { /* properties */ };
};
 
C++标准没有说class的layout应该怎么安排。
每个access secion中的data,必须按声明顺序。
但不同access secion之间,可以打乱顺序 —— 这个也许会有一些帮助。
 
第2种方案依然需要计算offsetof…… 这是很不靠谱的……
 
 
3.
作为base。
 
struct properties
{
        union
        {
                proxy1 property1;
                proxy2 property2;
                proxy3 property3;
                // ...
        }
};
 
class C : public properties
{
        // data
};
 
这样,可以很容易计算出C的位置:
void property_proxy::operator=( ... ) {
        void* p = this;
        C* c = static_cast<C*>( static_cast<properties*>(p) );
}
 
编译器会正确计算出C*,即使在多继承下也行。比如:
class C : other , public properties {};
 
但是,在msvc和gcc上,properties 都不会执行空基类优化…… 很囧……
仔细安排data的布局(在这2款编译器上,将小对齐的数据放前面),也会影响整个类的大小。
 
 
2和3的方案,如果能钻到空子,就可以几乎没有代价的实现property。
空子就是说,那个class本来就有一些需要被填充的空洞……
 
如果没有空可钻……  或者没有正确安排位置…… 会多占用一些大小。
额外大小不超过class中所有域中需要最大对齐那个成员的大小。
额外大小和properties的数量是无关的, 多少个properties都占用这么多额外大小, union嘛……
 
 
4.
还有一种方案。
其实上面的date,缺陷在于time_t time_; 这个成员会被外界访问。
 
可以这样:
class date {
        class impl {
                time_t time_;
                friend class date;
        };
public:
        union
        {
                impl impl_;
                proxy1 property1;
                proxy2 property2;
                proxy3 property3;
        }
        // ...
};
 
这样,客户代码可以访问的,只有impl_这个名字。
但是几乎不能拿它作任何事情。
连它的类型名 —— date::impl —— 都是不可访问的。
 
 
这个方案的缺陷就是 —— 通常必须从头设计一个类型。
union中不能放非pod。只能从内建类型开始,构造一些类。
 
比如已经有某个非pod的类,C,想将C作为一个成员,实现Cex:
 
class Cex
{
        union
        {
                C impl_; // 不行
        }
};
 
只能用上面的property base或者property member……
 
 
5.
范化工作 
这个…… 再说吧……
 如果能有更好的布局方案…… 范化工作就白做了……
 
re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-15 22:33
@cexer
关键点在于:"如何通过proxy地址,得到object地址"。
 
1. 可以给proxy放入一个object的指针。每个proxy消耗一个指针的空间。
太浪费了……
 
2. Imperfect C++中利用offset来计算object的地址,使得每个proxy不含任何域。
但C++为了区别各个proxy,会给每个proxy进行填充,依然会造成消耗。
 
 
3. 避免proxy在Record中被填充
假设有如下记录:
Record
{
        proxy1 property1;
        proxy2 property2;
        proxy3 property3;
        ...
}
其中proxy1,proxy2,proxy3, ...  是空结构。
 
Record r;
r.property1;
r.property2;
r.property3;
...
 
在C/C++中,如果想要property1、2、3、... 不占空间,也就是它们必须拥有相同的地址。
也就是说…… Record 是union……
 
 
4. 避免Record在class中被填充
即使这样,整个union依然是会被填充……
所以,可以将class的数据和union放在一起,就真的不消耗任何空间了……
(如果类本身不需要数据,那这个类也是会被填充的,proxys依然没有消耗多余的空间)
 
 
代码:
class date
{
private:
        
struct second_proxy
        {
                
operator int() const
                {
                        
const void* o = this;
                        
int sec = static_cast<const date*>(o)->get_second();
                        printf(
"%d second_proxy::operator int(%p);\n",sec,o);
                        
return sec;
                }
                date
& operator=(int sec)
                {
                        
void* o = this;
                        printf(
"second_proxy::operator=(%p,%d);\n",o,sec);
                        date
* d = static_cast<date*>(o);
                        d
->set_second(sec);
                        
return *d;
                }
        };

public:
        union
        {
                time_t time_;
                second_proxy second;
        };

        date()
        {
                printf(
"date::date(%p);\n",(void*)this);
                time(
&time_);
        }

        
int get_second() const
        {
                
int sec = localtime(&time_)->tm_sec;
                printf(
"%d date::get_second(%p);\n",sec,(void*)this);
                
return sec;
        }

        
void set_second(int sec)
        {
                printf(
"date::set_second(%p,%d);\n",(void*)this,sec);
                tm t 
= *localtime(&time_);
                t.tm_sec 
= sec;
                time_ 
= mktime(&t);
        }
};

second_proxy hour和date的数据time_t time_放在同一个union中。
这样,它们拥有相同的地址。
在second_proxy::operator int() const; 中,可以直接将this转换到const date* ……
就这样得到了…… object的地址……
 
 
在C中,必须给那个union取一个名字:
struct date
{
        union
        {
                time_t time_;
                second_proxy second;
        } must_have_a_name;
};
struct date date;
date.must_have_a_name.second; 必须这样访问……
 
C++中有匿名联合,所以可以直接:
date d;
d.second;
 
 
这样的坏处也很明显……
客户代码可以访问 d.time_;   什么都完蛋了……
 
 
我再想想怎么隐藏……
 
re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-15 22:02
@cexer
我想到一个几乎没有消耗的方案……
我再完善一下……

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-15 20:31
@cexer
Imperfect C++中的差不多看明白了……
proxy是包含在object中,通过proxy在object中的offset得到object的地址……


re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-15 19:57
我晕……
侵入式的property……

强大……

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-15 19:43
我去看了看Imperfect C++。因为是英文的,以前只看了一部分就停了……
我觉得前面都写得不错,ABI、mangling这些问题确实是很困扰……

但property…… 我觉得这并不算C++的Imperfection。
完全是被其他语言给蒙蔽了。

一门语言就应该有一门语言的样子。
比如用C,如无绝对必要,就不应该模拟出虚函数与异常机制。
书里面提到的7种优势,除了最后一种值得考虑,其他全都不值得称作优势。

property,C#中的,或者imperfect cpp 里面看起来的样子,无非就是让如下语法:
o.p( value ); o.set_p( value );
value = o.p(); value = o.get_p();

变成如下语法:
o.p = value;
value = o.p;

如果C++直接提供property的语法,那当然是好事。
但不提供,也不见得编程就进行不下去了,也没有绝对必要去模拟出这种东西。


书中提到的7个好处,除了最后1个,其他都不是property带来的好处。

1. 读写控制
非property一样可以实现读写控制

2. 内外表示转换
这依然是由Date的算法实现,而不是property的功劳。
不通过property,一样可以实现time_t作为内部表示,对外提供int get_xxx()

3. 验证
这个更搞笑了。
就在书中这句话的下面列出的代码,就明显说明对day的验证是通过:
void Date::set_DayOfMonth(int day);
完成的。

完全没有property的半分功劳。

4. 没看太明白
书中的意思是, Date.Now是一个缓存,Date::get_Now()是一个计算?
其实,这里真正需要的是"两种语意",而不是property。
这两种语意是:
Date::getNow();
Date::updateNow();

书中只是将getNow的语意,用property来表现;get_Now同时完成updateNow和getNow。


5. 可用性?
We didn't have to write date.get_Month(), just date.Month.
少敲点键盘而已……
如果利用重载,用void Month(value)代表set,用value Month(void)代表get,少敲的,就是一个调用操作符……

6. 不变式?
没看太明白。
没有property,也可以维护object的不变式,是吧?
只要将数据hide,通过public interface去操作,就可以了。

7. 范型编程
我觉得就只有这个说到点子上了。 而这个也就是模拟property的真正的作用:改变语法。
template<typename P>
void distance(const P& p1, const P& p2 ) {
假设这是一个计算2个点欧式距离的函数。 它已经通过:
p1.x;
这种语法,而不是p1.x(); 这种语法写成了。
}

如果新实现一个class, 想通过p1.x 这种语法访问,同时还要有读写控制等, 就需要模拟出property。
这可能是property真正的用处。




换句话说,真的存在很多情况,使得我们"不得不"使用如下语法:
o.p = value;
value = o.p;

来代替如下语法:
o.set_p( value );
value = o.get_p();
吗?

对这种情况,property模拟才是有价值的。


对很多情况,setter/getter的语法和property的语法都是可行的。
如果本身提供了property语法的语言,那使用2者之一都没关系。
但C++没有提供。对这种情况也非要去模拟出property的语法,就有点…… 为了property而property的味道了。


我继续去看书中怎么实现的……
我以前了解的实现方式,每一个proxy至少得带一个指针……
这消耗还是蛮严重的……


你怎么看?
re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-15 18:58
@cexer
没有完整的,就是你blog上发的一些片段……

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-15 18:57
@cexer
哦 原来是property模拟。

window.size
window.onCreated
是一个,呃,代理(proxy)对象?

size是一个代理,有一个=操作符和转换函数:
operator=(const Size& s) {
this->window->setSize(s);
}
operator Size() const {
return this->window->getSize();
}

这个代理对象也是要占空间的吧?


呃…… 还是等你的源代码与分析吧…… 不瞎猜了……
看来你也是难得抓到一个更新blog的闲暇……

re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-15 18:40
从这2行代码来说:
window.onCreated.add( &_handleCreated );
window.onCreated.clear();


从UINT message 到 onCreate 的这个分派(dispatch)过程,无论怎样都必定是一个运行时解析过程。
无论使用何种C++高级技巧,也不可能让它变为编译时dispatch。
因为message是一个运行时才能得到的值……
只是,这个dispatch的工作,可能不是由window所在的class完成,而是其他某个层次完成。
总之,这个层次一定存在,这个工作(运行时dispatch)一定免不了。
我猜得对吗……


接下来,框架将message 分派到onXXX之后,客户再将onXXX转发(forwarding)自己的handler这个过程,我相信是可以编译时确定的。
——因为我看过你以前的一些实现~_~

但是,编译时确定,就意味着运行时不可更改。
如果要运行时可更改 —— 如上面2行代码 —— 就一定需要一个"可调用体"(callable)来作handler的占位符(placeholder)。
是这样吗?
而C/C++里面,完全不占用一点空间的callable…… 是不存在的,对吧?


所以我很好奇window是如何零成本完成映射与转发,并且是一个空对象的。
映射肯定需要在某个地方做,可能不是window。
运行时可更改转发目的地而不使用数据, 好像…… 太不可思议了……


re: GUI框架:谈谈框架,写写代码 OwnWaterloo 2009-11-15 18:21
占个沙发,对这个很好奇:

"值得说一下,这种神奇的映射者是接近零成本的,它没有数据成员没有虚函数什么都没有,就是一个简单的空对象。就像传说中的工作能力超强,但却不拿工资,不泡公司MM,甚至午间盒饭也不要的理想职员。"

没办法…… 让它吃点亏好了:
template <typename DataStream>
DataStream& operator>>(DataStream& a_data, A& a_fileHeader);

该成这样:
template<typename C,class T>
std::basic_istream<C,T>& operator>>(std::basic_istream<C,T>& s,A& a);

这也是为iostream写扩展时要考虑的一个问题:
1. 要么严格使用标准库中的(w)i(f/string)stream,basic_i(f/string)stream
2. 要么放宽,使用任意重载了>>的。



二进制和文本关系到是否转换换行模式。

写入文件时是怎么写的?
>>和<<是格式化输出。
read/write是非格式化输出。
12
<<结果就是 '1','2' , >>时也希望是这样
write结果就是 0c,00,00,00, read时也希望是这样。

re: 函数调用栈初探 OwnWaterloo 2009-11-15 03:33
调试器需要符号表。

re: Concept 被移除,无缘 C++ 0x OwnWaterloo 2009-11-15 02:34
依然可以算C++0x……
0x嘛,大家都知道是16进制……
明年是C++0a,后年是C++0b ...

re: SFINEA in C++ OwnWaterloo 2009-11-15 02:33
现在和cnblogs的格式很相似了。
Windows Live Wirter可以导入pdf,然后发布到blog么?

或者Windows Live Writer在本地使用的什么格式? 可以diff(主要目的)么……

我有个想法是用某种文本文件格式的代码,比如html,latex,rst等,生成可以导入到Windows Live Writer的格式,再发布。
文本格式的代码可以diff……

共10页: First 2 3 4 5 6 7 8 9 10 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

常用链接

留言簿(8)

随笔档案(16)

链接

搜索

  •  

积分与排名

  • 积分 - 196688
  • 排名 - 132

最新随笔

最新评论

阅读排行榜

评论排行榜