博文
[笔记]template class的隐式转化(2007-12-30 18:29:00)
摘要:[Effective C++ 3rd 条款46]
有理数类class Rational是一个template<class T>,T的意思是做为表示分子和分母的数值类型.为了让它能够有相乘运算,可以写个函数
template<class T>
const Rational<T> operator*(const Rational<T> &l,const Rational<T> &r)
{ ... }
为了让它能够自动进行隐式转化,因为它有一个non-explicit的构造函数
template<class T>
class Rational
{
Rational(const T& n = 0, const T& d = 1){...}
};
所以希望这样的代码可以通过编译:
Rational<int> onehalf(1,2);
Rational<int> result=onehalf * 2;
因为那个operator*函数是一个模板函数,它并不等同于Rational<int>具现化后的那个函数,所以不能做隐式转化,实际上它不能通过参数推导.
解决方法是将该函数在template<class T>class Rational{}内申明为friend,因为在编译器看到Rational<int>的时候,就将Rational<int>具现出来,所以那个friend申明会爆光.
template<class T>
class Rational
{
...
friend const Rational<T> operator*(...);
};
然而,还是会引来连接错误,因为爆光的只是申明,对于那个外面的模板函数同样需要有机会具现,才能连接成功.最终,只能再加上内联,让函数的定义在那里爆光.
template<class T>
class Rational
{
...
friend co......
SGI STL中的alloc分析(2007-11-20 20:54:00)
摘要:我不是想重复<<STL源码剖析>>中第2章的内容,顺便广告一下,这本书写得很不错,看完那章回来就从http://www.sgi.com/tech/stl/download.html下载了一份源码,慢慢分析.
1,经典的内存池
为了满足一些小单位的频繁内存配置和回收,如果直接使用malloc()和free()等系统函数,将会使效率很低,同样也会造成很多内存碎片,加重系统垃圾回收的负担.内存池的设计初衷也正在此,将一大片内存一次配置,将小单位内存配置模拟OS的空闲块链,就相当于在中间增加了一层'软分配器',正如在主存和CPU之间增加的cache,小小的投入可以带来性能上的飞跃.但是,我们要问,为什么会这样?
这个问题可以从优秀的SGI STL中寻找答案.
2,SGI STL中的allocater
恰恰这份实现的STL并没有按标准,它是一个无参数的分配器,它有两套配置器,一个是
template <int __inst>
class __malloc_alloc_template;
只是一个简单的封装,其实还是用的C的malloc,从名字就可以看出来,当然它有自己的内存不足时的处理接口.另一个是
template <bool threads, int inst>
class __default_alloc_template;
这是我们需要研究的.可以在内部头文件stl_alloc.h中得到完整的源码.
它维护了一个空闲链的链表,共16个,每一个空闲链的块大小不同,分别是8,16,...,128,对于长为n的配置请求,如果n>128则调用第一套配置器,否则,将n上补成8的倍数,调用对应这16个链中的一个空闲链,从中摘一个块出来,如果链空,则再向后面的内存池请求一大块空间.总的看上去,它不是一个简单的单一的内存池策略,而是一个对不同大小的配置请求进行自适应的分配策略,具体情况如何不多说,下面分析性能.
3,性能的瓶颈在哪?
从应用的角度,操作比较少,简单来说无非是2个,申请allocate(n)和释放deallocate(n),申请的时候,如果空闲链非空,则只需摘一个块下来,速度是O(1),可以想像成几乎不需要时间.如果空闲链空,则需从后备池中拿一块出来,如果后备池中非空......
[笔试]奇怪的多态(2007-10-22 13:49:00)
摘要:今天做的一个题,问你输出是什么:
#include <stdio.h>
class B
{
public:
void f()
{
printf("B.f\n");
g();
}
virtual void g()
{
printf("B.g\n");
}
};
class D : public B
{
public:
void f()
{
printf("D.f\n");
}
void g()
{
printf("D.g\n");
}
};
int main()
{
B *p=new D;
p->f();
delete p;
getchar();
return 0;
}
原来真没看到过,赌了一把,回来用VC、DevC++编译了发现。。。
PS:一个基础的一时弄错了,记住了,先构造基类,析构是相反的过程。......
const指针和引用(2007-10-18 16:58:00)
摘要:很久没写BLOG了,不知道还有没有人对这里有兴趣,呵~
const在面试的时候考得多,特地小结一下。
1,const是修饰符,它表示某个对象是常量,即不能修改,而C++没能彻底抛弃指针这个C留下的遗产而有些尴尬,指针是比较圆滑的家伙,它有双重身份,有时表示它自己,有时表示它所指的对象(所以C++建议我们尽量用引用而不是指针)
const double e;// error
const double pi=3.14;// ok,需要初始化
这样的用法没什么好说的。
2,指针带来的问题
像这样写:
int a,b;
const int *p=&a;
实际上没有定义一个常量指针,而是指向常量对象的指针,这意味着后面可以这样
p=&b;//ok
a,b不是常量而是变量,这是可以的,事实上编译器不管这个,而是管*p和p->这样的运算不能被修改
*p=1;//error
就是说上面那句定义的并不是一个常量,它相当于
int const *p;
实际上是个变量。
而真正的常量指针怎么写?这样
int * const p=&a;//ok
int *const p;// error
它的意思是p不能再改,只能赋值一次,这实际上是什么,是引用。引用和指针有着这样一个巨大的差异。
上面ok的代码,实际上可以写成
int &p=a;
也就是说根本不需要常量指针,因为有引用(?)
3,引用和const
引用有着针指的优点,但是它的身份是确定的,不像指针有双重身份,所以用const修饰没什么问题。
const int &p=a;
它的意思实际上和这样写差不多
int const * const p=&a;
本身一次赋值,所指对象也不能改。这就是喜欢考的那个所谓,一个指向一个常量对象的常量指针。
试一下这么写
int const & const p=&a;
多此一举,在VC上会给出个警告,因为在语义上是重复的。......
我写的智能指针auto_ptr(2007-03-20 17:37:00)
摘要:为什么要用这个auto_ptr,一般的C语言指针不够用吗?如果C++里没有new,那一般的指针就够用了,现在的代码都像这样:
int *p=new int[100];
//使用p
delete[] p;
使用p的时候,如果突然出了问题,比如丢出异常,就会中止程序,而不能运行delete,造成内存泄漏,所以在使用的时候,经常要做很多判断,比如
if(p)
{
...
delete[] p;
}
if(XXX)
{
delete[] p;
throw(xx);
}
所以,拿到内存容易,但责任就大啊,所以整个代码被整个乱七八糟,而且程序员还要保持清醒的头脑,一不小心就。。。
STL的auto_ptr就是为了解决这个问题,做的一个封装,在指针的运算过程中,比如*,->,发生错误,丢异常,并在析构的时候delete。
把VC6里的auto_ptr复制下:
template<class _Ty>
class auto_ptr {
public:
typedef _Ty element_type;
explicit auto_ptr(_Ty *_P = 0) _THROW0()
: _Owns(_P != 0), _Ptr(_P) {}
auto_ptr(const auto_ptr<_Ty>& _Y) _THROW0()
: _Owns(_Y._Owns), _Ptr(_Y.release()) {}
auto_ptr<_Ty>& operator=(const auto_ptr<_Ty>& _Y) _THROW0()
{if (this != &_Y)
{if (_Ptr != _Y.get())
{if (_Owns)
&nbs......