随笔-90  评论-947  文章-0  trackbacks-0

如果要 typedef,搞出来的都是些很恶心的名字,自己看了也生气,还是不搞好了,就 size_t 和 int 玩到底吧。

把 Array 的接口又改得一塌糊涂了,重新贴一下:

namespace xl
{
   
template <typename ValueType>
   
class Array
   
{
   
public:
       
Array(size_t nSize = 0);
       
Array(size_t nSize, const ValueType &tValue);
       
Array(const Array<ValueType> &that);
        ~
Array();

   
public:
       
class Iterator
       
{
       
public:
           
Iterator();
           
Iterator(ValueType *pValue);
           
Iterator(ValueType *pValue, ValueType *pStart, ValueType *pEof);
           
Iterator(const Iterator &that);

       
private:
           
ValueType *m_pStart;
           
ValueType *m_pEof;
           
ValueType *m_pCurrent;

       
public:
           
ValueType &operator * ();
           
ValueType *operator -> ();

       
public:
           
Iterator &operator = (const Iterator &that);
           
bool operator == (const Iterator &that) const;
           
bool operator != (const Iterator &that) const;

       
public:
           
Iterator &operator ++ ();
           
Iterator operator ++ (int);
           
Iterator &operator -- ();
           
Iterator operator -- (int);

       
public:
           
Iterator operator +(int nDistance) const;
           
Iterator operator -(int nDistance) const;
           
Iterator &operator +=(int nDistance);
           
Iterator &operator -=(int nDistance);
        };

       
class ReverseIterator : public Iterator
       
{
       
public:
           
ReverseIterator &operator ++ ();
           
ReverseIterator operator ++ (int);
           
ReverseIterator &operator -- ();
           
ReverseIterator operator -- (int);
        };

   
public:
       
Iterator Begin() const;
       
Iterator End() const;
       
ReverseIterator RBegin() const;
       
ReverseIterator REnd() const;


   
public:
       
Array<ValueType> &operator=(const Array<ValueType> &that);
       
bool operator==(const Array<ValueType> &that) const;
       
bool operator!=(const Array<ValueType> &that) const;

   
public:
       
ValueType &operator[](size_t nIndex);
       
const ValueType &operator[](size_t nIndex) const;

   
public:
       
bool Empty();
       
size_t Size() const;
       
void SetSize(size_t nSize);

   
public:
       
void Insert(const Iterator &itBeforeWhich, const ValueType &tValue);
       
void Insert(const ReverseIterator &itBeforeWhich, const ValueType &tValue);
       
void PushFront(const ValueType &tValue);
       
void PushBack(const ValueType &tValue);
       
template <typename IteratorType>
       
void Insert(const Iterator &itBeforeWhich, const IteratorType &itFirstToInsert, const IteratorType &itAfterLastToInsert);
       
template <typename IteratorType>
       
void Insert(const ReverseIterator &itBeforeWhich, const IteratorType &itFirstToInsert, const IteratorType &itAfterLastToInsert);
       
Iterator Delete(const Iterator &itWhich);
       
ReverseIterator Delete(const ReverseIterator &itWhich);
       
void PopFront();
       
void PopBack();
       
Iterator Delete(const Iterator &itFirstToInsert, const Iterator &itAfterLastToDelete);
       
Iterator Delete(const ReverseIterator &itFirstToInsert, const ReverseIterator &itAfterLastToDelete);
       
void Clear();
       
void SetValue(const Iterator &itWhich, const ValueType &tValue);
       
void SetValue(const ReverseIterator &itWhich, const ValueType &tValue);
       
void SetValue(const Iterator &itFirstToSet, const Iterator &itAfterLastToSet, const ValueType &tValue);
       
void SetValue(const ReverseIterator &itFirstToSet, const ReverseIterator &itAfterLastToSet, const ValueType &tValue);

   
private:
       
ValueType *m_pData;
       
size_t m_nSize;
       
size_t m_nStart;
       
size_t m_nEof;

   
private:
       
void Release();
       
size_t GetWellSize(size_t nSize) const;
       
void MoveData(size_t nIndex, size_t nCount, int nDistance);
       
void CopyData(size_t nIndex, size_t nCount, ValueType *pNewMem) const;

    };
}

主要的考虑,还是想实现“跨容器的 iterator”(抱歉,我还是觉得这么称呼挺符合我的预期的)。只是,在没有语言层面的 concepts 支持的情况下,如何显式地让用户知道模板参数要符合哪些条件呢?

在这里再次感谢一下 OwnWaterloo 同学,上篇评论里的东西我会继续慢慢琢磨的。^_^

posted on 2009-09-28 23:13 溪流 阅读(719) 评论(18)  编辑 收藏 引用 所属分类: C++

评论:
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 00:28 | OwnWaterloo
显示让用户知道么……
有约定俗成
再不成就文档
再不成…… 只能看源代码了……

好消息是, 违反concepts一般都错在编译时,不会将错误留在运行时……
  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 09:32 | 溪流
@OwnWaterloo

如果有个 template <T> where T is IIterator,用户看接口定义就知道该怎么做了;可是现在,只能到调用 Iterator 的某个具体方法的时候才报错,用户理解这个错误,就要理解我的实现了。这不仅增加了学习代价,还在一定程度上违背了封装的初衷,不是么?一点变通的办法都没有吗?
  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 15:55 | 陈梓瀚(vczh)
如果你对效率要求不是跟人家开发SqlServer的后台一样高的话,那就来一个虚基类吧,当成C#的interface用用。虚基类的作用主要在于你可以用他搞类型计算,譬如说来一个iterator你就可以知道它的element type是什么,而不用跟stl里面的一样要求你必须在iterator的里面typedef。而且你还可以做很多iterator<A>到iterator<B>的转换,从map<a,b>到iterator<pair<a,b>>的转换等等。就是慢一点点罢了(真的是一点点)。  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 15:56 | 陈梓瀚(vczh)
@OwnWaterloo
坏消息是,那些编译错误根本看不懂。  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 15:56 | 陈梓瀚(vczh)
@陈梓瀚(vczh)
我写了一个错误的iterator然后错误消息竟然是list<T>里面的哪个代码用了一个不存在的函数  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 16:09 | 陈梓瀚(vczh)
@溪流
template<typename T>
class IEnumerator
{
public:
virtual int Index()const=0;
virtual const T& Current()const=0;
virtual bool MoveNext()const=0;
virtual void Reset()const=0;
virtual bool Available()const=0;
virtual IEnumerator<T>* Clone()const=0;
};

template<typename T>
class IEnumerable
{
public:
virtual IEnumerator<T>* CreateEnumerator()const=0;
};

//注意使用方法
void MyCopy(List<int>& dest, const IEnumerable<int>& source)
{
....
}  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 16:14 | 陈梓瀚(vczh)
@溪流
而且我认为作为一个iterator,我绝对不会想到是Array.SetValue来通过它修改自己的值的  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 18:33 | 溪流
@陈梓瀚(vczh)

virtual IEnumerator<T>* CreateEnumerator()const=0;

这个,到使用的时候:
IEnumerator<T>* p = CreateEnumerator();
然后求用户去 delete 吗?  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 18:33 | 溪流
@陈梓瀚(vczh)

嗯……iterator 一般有哪些约定成俗的规则?  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 18:35 | 溪流
@陈梓瀚(vczh)

我觉得,出现在具体实现上的编译错误,本不应该留给用户的。最好是在类型检查的时候就能报错。  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 20:23 | OwnWaterloo
OO为什么被人吹捧?
可以说, 是它开创了一个历史,人们开始普遍采用 "对抽象的、多态的事物编程"。
 
在那之前, 无论是OP还是OB, 都只能完成模块化, 方便分工开发与测试。很少使用多态技术。

OB编程(OP就省了…… OP->OB依然能完成一定复用,但方式不同):
void f_ob(T v) { v.f(); }
 
f是针对一个具体的T进行编程。
f的行为和T的行为紧紧绑定在一起。
f想要表现出不同行为, 必须要T表现出不同行为。
但无论T如何改变,T和f都只具有一种行为,最后一次改变后具有的行为。
 

OO编程:

void f_oo(I& i) { i.f(); }
 
f现在的行为, 仅仅与i所表现出的契约绑定在一起。
而i可以有各种各样的满足契约的方式
这样的一大好处就是, 对不同的D : I, f可以被复用
得到这一好处的前提就是, D 满足 I与f之间的契约。
 

GP编程:
template<typename T>
void f_gp(T v) { v.f(); }
 
同样提供了多态行为:以不同的T带入f, 就能得到不同的行为。
使得f能被复用。STL中的组件, 大部分是通过这种方式被复用的。
并且,STL组件之间, 也大部分是通过这种方式复用的。
 

1.
无论是GP还是OOP都是组合组件的方式。
它们(典型地)通过concepts 和 interface来抽象出组件多态行为。
对这种抽象事物编程得到的结果(函数/类模板,包),可以被复用(软件工程一大目标)。
-------- -------- -------- -------- -------- -------- -------- --------
 

上面提到契约。
无论是OP、OB、OO、GP, 组件间要能协同工作, 都是需要契约的。
 
OP:
free 可以接受空指针, 而printf 不行。
前者是free对调用者定下的约束, 后者也是printf对调用者定下的约束
—— 要使用我就必须这样做。
 
malloc 失败返回空指针, 而new 抛异常, 否则返回可用动态内存。
这是它们对调用者的承诺。
—— 使用我, 你能得到什么。

T* p = new T;
if (!p)  这种代码只在古董编译器上才可能有意义。
 
"加上NULL判断更好" , 也只有"C++方言"的古董学者才说得出来。
 
new[] 要 delete[] , new 要 delete, 这也是契约。
"因为char是基础类型,所以可以new[] , delete", 只有不懂软件契约的白痴学者才说得出来。
 

OB:
要将CString s 转换为 TCHAR*, 一定要用 s.GetBuffer  而不是 (TCHAR*)&s[0]
CString 的约束。
 
basic_string.c_str() 一定以'\0'结尾, 而data() 则不是。
basic_string  的承诺。
 

OOP:
我用的OOP库、框架不多……  举不出什么例子。
但它的契约和通常OB"非常类似" : 完成什么、需要调用什么、调用顺序、 参数合法性。
 

GP:
GP总共有哪些契约形式, 我总结不出来。
但至少有一条 —— 它不再对T有完全限定, 而只作最小限定
 

还是上面的代码:
void f_oo(I& i ) { i.f(); }
D d;
f(d); // 要求 D : I

template<typename T>
void f_gp(T v) { v.f(); }
要求  v.f(); 合乎语法 :比如, 它既可以是non-static member function, 也可以是static member function。
并且仅仅要求这一点
 
 
2.
契约是普遍存在的, 不仅仅是GP、 其他范式都有。
它是合作与复用的前提。
-------- -------- -------- -------- -------- -------- -------- --------
 
 
3.
为什么GP饱受争议, 而OO没有?
我觉得, 是因为从OB到OO过度比较自然
 
 
要求一个I需要怎样, 一个I需要使用者怎样的时候,
通常也会有类似的需求 —— 要求一个C怎样, 一个C需要使用者怎样。
注意, 这只是 client 与 I, 以及 client 与 C之间的契约。
在这个层面上, 不需要学习新的思想。
而下面会提到OO引入的新的契约形式。
 

而GP的契约形式, 都是一些全新的形式
其中最主要的形式是: T 是否具有一个叫f的函数(操作符也可以理解为一种调用函数的方式)。
在C++中, 还有必须有某个名字的嵌套类型, 通过T::f强制static member function等形式。

OO比较流行、支持OO的语言比较多,是目前主流。
C++,Java,C#的开发者总共占了多少比例?
 
GP支持的语言其实也多。
但抛开C++(或者算上C++用户中理解GP的人)怎么都比不上上面3个巨头。
 

批评自己不熟悉的事物是不严谨的。
遇见STL编译错误, 要么就去学习GP的方式, 要么就抛弃STL。
抱怨STL是种傻逼行为 —— 明明是自己不会用, 要怪只能怪自己。
 
 
-------- -------- -------- -------- -------- -------- -------- --------
4.
如同GP、 OO同样需要学习、 需要文档、 否则会充满陷阱
 
上面提到的client 与 I的契约形式通常和 clinet 与 C之间的形式相同。
使得OO的一个方面可以"温固"。
 
而client 与 D之间的契约? 这个层面不"知新"是不行的。
并且这个层面上的契约常常是出bug的地方 —— 因为这是语法检查不了的, 必须有程序员自己去满足语意。
 
举个例子 :
看见一个虚函数,它是否可以被覆盖? 覆盖它的实现是否需要同时调用基类实现?
除非是约定俗成的一些情况, 比如 OnNotify、OnXXXEvent这种名字比较明显。
其他情况, 不去看文档, 依然是不知道的。
 
 
我刚刚就犯了一个错。
python 中 继承HTMLParser , 覆盖__init__ 时, 必须调用基类实现。
我确实不知道构造函数也可以被完全覆盖……(原谅我…… 第1次使用python……)
在C++中, 基类的构造函数是无论如何都会被调用的。
 
 
我没有调用基类的__init__, 然后报了一个错。
好在报错清晰, 源代码的位置都有, 源代码也可见, 源代码也清晰。问题很容易就找出来了。
 
 
如果
4.1.
它不是构造函数, 只是一个普通的虚函数?
名字也看不出什么蹊跷?
 
4.2.
文档也没有记载是否需要调用基类?
(HTMLParser中的文档没有说要调用__init__, 这应该是python的惯例, 所以就没有记载了)
没有严格的错误检查?
没有完整的、信息丰富的call stack trace?
等发现错误时, 也许都离题万里了。
 
4.3.
没有清晰的源代码(其实到了要查看源代码的时候, 已经是……)?
 

这些问题在OO中同样是存在的, 只是它没引起编译错误, 或者说编译错误比较明显, 容易修改。
而更多的语意检查, OO中 client 与 D之间的层次, 依然要靠程序员的学识 —— 主要是查阅文档的习惯。

对比GP
4.1之前的4.0(OnNotify, OnXXXEvent之流), 同样存在一些约定俗成。
 
对4.1, 同样是查文档。
 
对4.2, 没有文档同样犯错。
C++中, 一部分错误可以提前到编译时(C++只在持编译时支持ducking type)
 
对4.3
同样需要去查看源代码。
 
GP的代码比OOP难看? 
同上面, 只是因为不熟悉、不信任、不需要这种抽象方法, 这些契约的形式。
 

STL中的契约形式其实并不多。
[first,last) 左闭右开区间;
iterator的几个种类;
(adaptable)binary(unary)_function;boost只是将其提升到N-nary,还省去了adaptable的概念;
相等、等价、严格弱序。
多吗?
 
而且, 我不觉得为了比较要去实现一个IComparable 有何美感可言……
 
 
-------- -------- -------- -------- -------- -------- -------- --------
5. 这些新的契约形式、 抽象方式, 有没有其优势
当然有 —— 最小依赖带来的灵活性
上面也提到, GP只依赖于其需要的契约, 对其不需要的, 通通不关心。
 
 
现在详细解释上面
D d;
f_oop(d); // D : I
 
T v;
f_gp(v);    // v.f
 
为什么后者比前者灵活。因为后者只依赖所需要的东西。
 
5.1
template<typename T>
void f123_gp(T v) { v.f1(); v.f2(); v.f3(); }
 
可以使用
struct T123_1 { void f1(); void f2(); void f3(); }
struct T123_2 { void f1(); void f2(); void f3(); }
 

void f123_oop(I& i) { i.f1(); i.f2(); i.f3(); }
必须有一个
struct I { virtual void f1(); virtual void f2(); virtual void f3(); };
然后是:
D1 : I; D2 : I; ...
 
 
5.2
如果现在需要实现另一个函数, 它对T的需求更少 :

template<typename T>
void f12_gp(T v) { v.f1(); v.f2(); }

T123_1, T123_2 可以使用
 
新增一个:
struct T12_1 { void f1(); void f2(); }
依然可以使用
 

OOP就必须重构了:

struct I12 { virtual void f1(); virtual void f2(); };
struct I123 : I12 { virtual void f3(); }
struct D12 : I12 {};
 
5.3
再看 :
template<typename T>
void f23_gp(T v) { v.f2(); v.f3(); }

T123_1, T123_2 依然可以使用
T12_1 不行。
 
但新增的
struct T23_1 { void f2(); void f3(); }; 可以使用
 
 
OOP又必须重构:
总体趋势是这样的, OOP需要极端灵活的时候, 就会变成这样:
一个接口, 一个函数
struct I1 { virutal void f1(); };
struct I2 { virutal void f2(); };
struct I3 { virutal void f3(); };
现在接口设计是极端灵活了。

但使用接口时, 依然逃不过2种都不太优雅的作法:
1. 接口组合
struct I12 : I1, I2;
struct I23 : I2, I3;
struct I31 : I3, I1;

2. 接口查询
不组合出那些中间接口, 但运行时作接口查询:
void f12(I1* i1) {
  i1->f1();
  if (I2* i2 = dynamic_cast<I2*>(i1) {
     i2->f2();
  }
  else { 将一部分编译时错误留到了运行时。 }
}
这不是故意找茬, 而是将STL中的iterator换个简单的形式来说明而已。
 

也许绝大部分情况下, 是不需要灵活到每个接口一个函数, 而是一个接口3、4个相关的函数。通常它们会被一起使用。

即使没有上面如此极端, 假设IPerfect1、IPerfect2都是设计得十分合理的, 3、4个函数的接口, 通常这3、4个函数要么必须一起提供, 要么都不提供, 单独提供是不符合语意的, 提供太多又是不够灵活的。
这需要经验, 相当多的经验。 但总是可以完成的事情。
 
但组合接口, 依然是OOP的痛处。
我记不清C#和Java中的interface是否继承自多个interface
如果不行, 它们就可能需要运行时接口查询。
而C++, 要在这种"组合接口"与接口查询之前作一个选择。
 
反观GP, 它一开始就不是以接口为单位来提供抽象,而是按需而定
所以, 它既不需要仔细的拆分接口, 也不需要组合接口。
STL中数据、容器、算法相互无关、可任意组合。
应该是前无古人的突破。
后面有没有来者? 上面已经说了, OOP要达到这种灵活性, 同样也有其代价。
并且, OOP代价体现在丑陋, 而不是难以理解
 

灵活的事物肯定比不那么灵活的事物难理解,抽象总比具体难理解。
所以抽象出一个合理的、广泛接受的语意很重要。

* 就是解引用, ++ 就是前迭代, -- 就是后迭代。
支持--就是双向, 支持 + n 就是随机。
 
GP也不会胡乱发明一些语意不清晰的概念。
window w;
control c;
w += c; 这种代码在GP界同样是收到批评的。
 
 
最后, 软件工程中, 是否真正需要灵活到如此程度, 以至于大部分人难以(或者不愿意去)理解的事物, 我就不知道了……
  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 22:16 | 溪流
@OwnWaterloo

读了这么多文字,真是受益良多。

但必须指出的是,上面对于 OOP 和 GP 的某些比较是不公平的。在 OOP 中,只有你需要去继承它的时候(即你需要修改这个类),你才需要了解源代码,需要了解跟实现相关的“契约”。如果仅仅是使用的话,那么,给出全部 public class 的 public method 的声明,应该所有的使用者都不会犯语法错误,编译器会提供完备的类型检查。而同样是使用(并不是去修改),GP 中,却需要去了解它的实现,这不是很不公平吗?我们不是追求为了隐藏不必要的细节,让使用者减轻负担吗?当然,如果要去修改它,那么对它的实现是必须要了解的了。
  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-29 23:07 | OwnWaterloo
@溪流
嗯嗯, 你说得很对。

假设多态的使用者与多态抽象之间的层面叫A, 还有多态提供者与多态抽象之间的层面叫B。
OOP中的A和OB很相似, 只有B是新加的内容。
而GP中, 无论A、B, 都是新鲜内容。

所以这样比较是不公平的~_~


我至今没有完全明白rbtree的实现, 但不妨碍我使用map和set。
对一般的内建类型, 或者符合STL规范的值类型, 直接使用即可。
如果需要放自定义类型,需要了解的就是key_compare和严格弱序。
也不算多吧? OOP同样离不开比较准则, 只是key_compare换成了ICompare。
如果还想高级一些, 还可以去了解allocator, 那是篇幅更长的一套规范。
OOP没见有这功能, 不比较了。

boost中有一些库, 我知道实现原理, 但不知道对付各种编译器的trick。
还有一些隐约知道原理, 更多的是不知道。
boost的源代码我一份都没看过(坚持不下去…… 确实很丑陋, 但很多代码都是为了对付不同编译器而已)

举个例子, any。 即使没看源代码, 我也知道这个库应该在何时使用, 以及如何使用 —— 因为我看了文档…… 不多的。 interface也需要看文档的吧?

但就是这么一个小东西, 使得我可以通过一个参数传递任何东西。
并且不再需要继承自某个基类……

"一切皆为Object" —— 我觉得这是很可笑的事情。
Finder.find?
p1.fuck(p2); 还是 p2.fuck(p1);? 3p呢?
太可笑了……

而且在java和C#中实现free function其实并不难, 只是它们固执, 不愿意加入而已。

OOP根本就不是一种纯粹的编程范式。 不结合其他范式, OOP根本就写不出程序来。 不知道那些追求所谓"纯OO"的家伙是怎么想的 ……
在我眼里, OO只有oo analysis, oo design, oo programming只是oo design而已。 实现design时, 是命令式的。
至少对java, c#的OO来说是如此。

不是我一人这么说, 你还可以去看看《冒号课堂》。


上面有点小错误, 纠正一下:
C/C++中本来就可以以单一参数传递任何东西……
any 是使得这一作法类型安全而已。
当然, 既然使用C/C++写代码, 不同与那些保姆语言, 程序员自己就要对类型安全负责。 所以any通常是为了堵住那些对类型安全尤为重视的人的嘴而已。
我还是更喜欢void* ...

真不知道那些成天叫嚣着类型安全, 却视generic加入java是一种退步, 使得java不纯粹的人是怎么想的……
难道"不犯错", 比"犯了错总有人补救" 更重要?  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-30 00:04 | OwnWaterloo
上上面关于interface和concepts的比较, 是因前不久给一个项目经理解释ducking type和以函数为单位的抽象形式, 但没成功……

现在算是想得比较清楚了, 但好像没表达清楚…… 那么多文字, 我自己都不愿意看第2遍……


整理一下:

对单个"契约"的设计而言, 通过interface或是concepts没有什么区别。
并且interface比concepts更广为人知。
所以当时怎么都没能说服他……

interface的劣势(也就是concepts的优势)体现在"如何拆解与组合契约"上。
如何拆分,设计interface, 是门学问。
那几个方法通常是应该整体提供的?
这就是interface的劣势的本质 —— 它总是要将若干方法"打包"到一起才可以。
极端情况, 只含有一个方法的interface也很常见, Dispose所属接口就是。

interface更麻烦的地方在于组合。
当一个f需要若干种interface的功能时, 要么必须将一些interface继续"打包", 形成一个新的, 要么运行时检查。


而concepts打从一开始, 就仅仅依赖所需要的东西, 不需要"打包"这一过程。
拆分和组合是自由的。
从某种程度上来说, 这也容易造成晦涩的设计。
但什么设计不是学问呢…… interface的设计同样需要经验。


concepts相比interface来说, 限制更少, 也更灵活。
这种灵活性是本质上的 —— 因为不存在"打包"这一限制 ——是不以现实编程中是否需要这种程度的灵活性,以及这种灵活性是否会被人们普遍接受而转移的。


灵活的事物通常难以掌握, 如果现实编程中不需要, 人们不理解、不接受, 也只能算是当前条件下的劣势 —— 对太多数人来说晦涩、复杂, 无法被广泛使用。
如果哪天人们接受了呢?



还有一点, 上面说的GP和OO、OOP概念太广泛。 OO、OOP的定义到底是什么, 各有各的说法。
所以改为 concepts 和 interface( in java and C# )的比较, 比较准确。
并且某些其他支持OO的语言 —— 现在是个语言都要说自己支持OO, 不支持的也要改为支持... ——中, 是不需要interface这种"契约打包器"的。



楼主啊, 我自我感觉比较好的建议, 就只有建议你实现一套侵入式容器(包括树式堆)而已, 既锻炼了技巧, 又不陷入"重复发明轮子"。

其他的, 都是自说自话 …… 不必想太多-_-

因为做上这行了,没什么时间总结平时的一些零散想法。
如果是中学做习题, 会因为做着做着, 就累了 …… 就转而"总结一下吧,比单纯做来得有效, 同时休息休息"。
而写代码…… 是没有累的感觉的…… 就会一直写下去……
只有在讨论的时候, 才会想去总结一些东西。
把你的blog评论区当吐槽了…… sorry ...
  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-09-30 00:49 | 溪流
@OwnWaterloo

非常欢迎吐槽~哈哈
关于侵入式和非侵入式,说实话我也是前几天第一次听你说。我稍微查了下,不是很确切的了解其含义。前面我在 Iterator 里放了个 Array *m_pArray,后来拿掉了,改成放一个 ValueType *,有没有改变侵入式/非侵入式方面的属性呢?  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-10-01 14:16 | OwnWaterloo
@溪流
我out了……

boost 已经有 intrusive 容器的实现了……
http://www.boost.org/doc/libs/1_35_0/doc/html/intrusive/intrusive_vs_nontrusive.html

我这里的boost版本非常老…… 1.33…… 所以没发现……  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-10-12 18:39 | 陈梓瀚(vczh)
@OwnWaterloo
当然interface比起纯GP还有一个好处,就是你可以对一个具体的类,譬如IEnumerable<int>得到类型推导结果int,但是你没法对一个随便什么object只是长得跟IEnumerator<int>一样的东西来得到类型推导结果int。这(类型推导)是GP更大的价值,可以跟OOP一起,OOP充当抽象,GP用来做推导,能做出很多漂亮的用法来。譬如说:

MyList<int> list;(继承自IEnumerator<int>)
list>>Where(某个函数)的结果一定是IEnumerator<int>,编译器推导了。

反而vector<int>也想要支持的话,你会发现所有的容器都要写一遍……所以我只需要让我的容器都支持IEnumerator<T>,然后GP对IEnumerator<T>进行类型推导即可,提供10个功能我只写10个函数。如果是vector<int>+list<int>+queue<int>什么的,提供10个功能要写10×n个函数,n==容器数量。

当然这仅仅是对于【容器】而言的。至于你刚才讨论的很多都是其他的东西,就不纳入了。  回复  更多评论
  
# re: 算了,还是不 typedef 了,类型真烦 2009-10-12 19:15 | OwnWaterloo
@陈梓瀚(vczh)
需求是iterator类型推导iterator解引用后的类型?
嘿嘿,真的不能吗?

#include <iterator>
MyIt it1,it2;
typename std::iterator_traits<MyIt>::value_type // 这就是你要的
v = *it1;

// 除此之外还有其他几种类型信息。
typename std::iterator_traits<MyIt>::pointer p = &v;
typename std::iterator_traits<MyIt>::reference r = v;
typename std::iterator_traits<MyIt>::difference_type d
= std::distance(it1,it2);

typename std::iterator_traits<MyIt>::iterator_category tag;
tag决定了iterator能做什么。

algorithm中的所有需要iterator的算法,都和容器完全无关。
和iterator相关的类型信息都是这么取得的。
自然不存在M*N(M=算法的数量,N=容器的数量)的说法。
而是M(M=算法数量)。


给你举个例子可能比较明显:
需要value_type的算法我一时还想不出来……
来个其他的需要difference_type的意思意思:
template<typename InIt,typename T>
typename iterator_traits<InIt>:: difference_type // 返回类型
count(InIt first,InIt last,const T& v) {
typename iterator_traits<InIt>:: difference_type c = 0;
for (;first!=last; ++first) if (*first==v) ++c;
return c;
}



这是GP的类型推导方式 —— 嵌入类型。这用法漂亮吗?


实现这一魔法的工作:
iterator_traits只需实现一次。
各个容器其实不需要做什么工作。
工作在每一个iterator类型上,它必须有这几种嵌套类型。
当然,iterator头文件中提供了std::iterator, 可以继承自它而得到这几种嵌套类型。
不过我一般喜欢自己写,继承可能会影响空基类优化。
多吗?


而且,有一点OOP是办不到的。 所有的iterator都不需要有一个"基类"。
T arr[size];
&arr[0] 类型是T*, 是iterator,而且还是random_access。但是它的基类应该是什么才好呢?
它只有4字节。OOP就那个vptr就4字节了,加点数据再加上padding,8字节至少。



"反而vector<int>也想要支持的话,你会发现所有的容器都要写一遍……"
这是什么需求? 需要所有容器都写一遍? 说出来看看?  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理