Kyle 头衔: 论坛版主
最近整理了一下曾经的学习笔记,现在贴到这也算是帮一些C++初学者复习一下,考虑到模板对于STL以及泛型编程非常重要,那我们就从模板开始,虽然很不全面,但我还是认为这些知识应该是比较关键而且容易混淆的。ok,let's go 下面先来看一个程序: //:test.h #include<iostream> using namespace std;
#ifndef TEST_H_ #define TEST_H_
template<class T> class A { private: T value; public: A<T>(T n=0):value(n){} A<T>& operator = (const A<T> &x) { value=x.value; } friend ostream& operator << (ostream& os, const A<T>& x) { os<<x.value<<endl; return os; } }; #endif
//:test.cpp #include<iostream> using namespace std; #include"test.h"
int main() { A<int> a(2); A<int> b; b=a; A<double> c; b=c; return 0; }
上面这个程序会发生一个错误,你发现了吗?那就是 b=c; 这条语句。由于 c的类型是A<double> ,然而在产生对象实例b的时候就已经限定了operator =的参数类型必须和b的类型相一致,即A<int>,因此便会发生类型不匹配的错误。 下面再来看一段程序:
//:test.h #include<iostream> using namespace std;
#ifndef TEST_H_ #define TEST_H_
template<class T> class A { private: T value; public: A<T>(T n=0):value(n){}
template<class U>//注意此处与第一段程序所发生的变化,此处是一个成员模板函数 A<T>& operator = (const A<U> &x) { value=x.value; }
friend ostream& operator << (ostream& os, const A<T>& x) { os<<x.value<<endl; return os; } };
#endif
//:test.cpp #include<iostream> using namespace std; #include"a.h"
int main() { A<int> a(2); A<int> b; b=a; A<double> c; b=c; return 0; }
上面这段程序也有一个错误,不知道你发现了吗?如果你仔细看一下这段程序与上一段之间的差别,那么你就会恍然大悟~~~~,对了,它发生了无法访问private成员的错误。此时b=c;并没有发生错误,因为operator =的参数可以与b的类型不同,可以是A<U>,而不必是A<T>.但这又引入了另一个错误,那就是value=x.value;这个错误,因为x的类型与*this的类型不同,所以必然会发生不能访问x的private成员。
OK,我们再来看一个程序,下面这个程序会向你展示template constructor的概念。
先来看一段程序:
//:test.h #include<iostream> using namespace std;
#ifndef TEST_H_ #define TEST_H_
template<class T> class A { private: T value; public: A<T>(T n=0):value(n){}
template<class U> A(const A<U>& x) { value=x.getvalue(); cout<<"template constructor"; }
T getvalue() const { return value; } friend ostream& operator << (ostream& os, const A<T>& x) { os<<x.value<<endl; return os; } };
#endif
//:test.cpp #include<iostream> using namespace std; #include"a.h"
int main() { A<int> a(2); A<int> b=a; A<double> c=a; return 0; }
看一下程序的运行结果,你会发现只打印了一次“template constructor”,但实际上调用了两次复制构造构造函数,这是为什么呢,原来就是因为template constructor的缘故,这里有一个规则,那就是template constructor并不遮蔽implicit copy constructor,当两个类型相同的的对象进行复制的时候,就会调用implicit copy constructor,然而当两个类型不同的对象进行复制的时候,就会调用template constructor,这也就是只打印了一次template constructor的原因。因此利用这个特性就可以实现复制时进行隐式类型转换。 怎么样,不知道上面三个问题都搞清楚了吗,如果我有错误的话,请告诉我,以后我会陆续贴一些帖子的,大家如果想探讨哪一方面的请告诉我。谢谢
magmng 头衔: 总版主
问个问题
第三个例里面,那两个地方调用了复制构造函数?
Kyle 头衔: 论坛版主
to magmng: 调用两次复制构造函数的地方是: A<int> b=a; A<double> c=a;
由于b和a有相同的类型,都是A<int>,所以它们并没有调用A(const A<U> &)这个模板复制构造函数,而是调用了编译器生成的复制构造函数,进行位拷贝。 又由于c和a的类型并不相同,一个是A<int>,一个是A<double>,因此它将调用A(const A<U>&)这个模板复制构造函数,实现了隐式类型转换。
magmng 头衔: 总版主
A<int> b = a; 调用了 cptor 吗? 这里只是简单的 bit copy 既然是 bit copy 就不会调用函数
Kyle 头衔: 论坛版主
这里是在初始化b,因此必然调用copy constructor.只不过这里的copy constructor 是由编译器生成的implicit copy constructor,因为你并没有定义A(const A<T> &) 如果你定义了,当然是调用你定义的copy constructor了,你可以定义一个看看
alanier 头衔: 论坛版主
在copy constructor 里面加上cout<<"copy constrcutor"<<endl;你就知道了 A<int> b = a;是否是在调用copy constructor。 如果是单纯的b=a那么就要调用operator =。
magmng 头衔: 总版主
正是因为你的那个例三的程序里面没有定义cptor 所以编译器只能采用默认的办法直接拷贝。 一旦你定义了cptor,编译器在做那个操作的时候是肯定要调用的。 但是如果你没有定义,编译器怎么可能调用cptor,根本就没有cptor嘛 然后说那个implicit,implicit究竟是个什么东西,它的语法和语义究竟是什么。 如果你设计编译器,而它的作用只是实现位拷贝,你会把它当作一个函数吗?直接就 memcpy 了。
Kyle 头衔: 论坛版主
编译器会生成copy constructor,就好比如果没有定义构造函数和析构函数,编译器会帮你生成一样
magmng 头衔: 总版主
看来话题越说越大了:) 如果你的一个 class 没有 ctor 和 dtor 你真的以为编译器会给你生成一个? 如果真的给你生成一个,那么它做那些工作?
Kyle 头衔: 论坛版主
它什么也不做,也就是说它的初始化意图是不明确的,我们在实际中应该避免这种情况。如果在自己没有定义一个默认构造函数,然而又不让编译器生成一个默认构造函数的话,是无法使用像A a;这样的语句进行定义一个对象的。例如你如果自己定义了一个带参数的构造函数,然而你又没有定义默认构造函数,那么编译器就不会为你生成一个默认构造函数。那么你就不能使用诸如 A a;这样的语句
magmng 头衔: 总版主
比如 #include <iostream>
using namespace std;
class test { int n; };
int main() { test t;
return 0; }
这个 t 在 main 的栈空间上分配 sizeof (test) 个大小的字节 虽然没有 test::test 来初始化这个 n 的值,但是这个 t 对象仍然是存在的
如果说有一个默认的 ctor 但是这个 ctor 具体做什么工作编译器不可能知道 于是只能是一个空函数,与其去调用这个空函数不如根本就不去做这个操作
Kyle 头衔: 论坛版主
那既然你说这个编译器生成的构造函数没有必要,那为什么当我们定义了一个带参数的构造函数的时候,如果我们再调用test t;编译器就会出错呢,就是因为那个默认构造函数不存在了。这样做有助于简化编译器的实现。
magmng 头衔: 总版主
又如 #include <iostream>
using namespace std;
class test { int n; public: test(int i) : n(i) { } void show() { cout << n << endl; } };
int main() { test* p = (test*)malloc(sizeof (test)); new (p) test(3); *((int*)p) = 3; p->show();
return 0; }
只要空间分配了,这个对象在内存中存在的条件就具备了 ctor 不过是按照程序员的要求初始化这段空间罢了。 如果把 placement new 那行注释掉 用 *((int*)p) = 3; 代替 和调用 ctor 有区别吗?
magmng 头衔: 总版主
一旦你给出一个 ctor 的实现,编译就认为你规定了初始化的方法,而且重要 当然不允许你用非规定的方法去初始化
如果你没有规定初始化方法,编译器认为你规定初始化的方法不重要 没有必要阻止你构造状态未知的对象
Kyle 头衔: 论坛版主
你现在所说的是关于new 与malloc的区别了,其实二者在底层语意上是一致的,只不过new完成了类型的转换,还有调用构造函数进行初始化,因为对于在heap上建立的对象来说,初始化显得极为重要。如果你非得用malloc,也没关系,只要你把所有的任务都自己完成。但是如果要进行继承的话,malloc就不太好办了吧,不过只要有耐心细心也好办:)
magmng 头衔: 总版主
哎 看来我们对于 new 和 malloc 有又…… 等这个帖子完了开个新帖继续讨论,ok?
Kyle 头衔: 论坛版主
~~~~呵呵,看来真的是我错了,我刚才反汇编了一些代码,对于没有定义默认构造函数的类果然在定义一个对象的时候编译器并没有生成默认构造函数。因为在汇编代码中看不到调用函数的影子。 不过,我记得在任何一本c++书上都会说如果没有定义默认构造函数的话,c++会产生一个什么事情也不做的默认构造函数。看来在C++和具体编译器的实现上还是有许多不同的. 谢谢magmng!
magmng 头衔: 总版主
呵呵,不仅是书上,就是 14882 都说 The implementation will implicitly declare these member functions for a will implicitly define them if they are used ... 我说的仅仅是“你真以为编译器会为你生成……”我指的是编译器的具体实现,至少在 VC 的编译器和 GNU 的编译器上都没有这个调用。(或许是中间代码有这个调用,但是被最后优化掉了呢?)
看来我们的分歧还是没有互相理解对方的观点:)
如冰 头衔: 论坛坛主
呵呵,理解万岁!