C++中几个值得分析的小问题(2)

下面有3个小问题,作为C++ Beginner你一定要知道错在哪里了。

1、派生类到基类的引用或指针转换一定“完美”存在?

一般情况,你很可能会认为:派生类对象的引用或指针转换为基类对象的引用或指针是一件很正常的事。那要是不一般情况呢?请看下面这个例子:

class Person
{
public:
    Person(const string& str = "Normal Person") : ID(str) {}
    string ID;                                //作为一般的人身份是“普通人”,作为学生身份是“学生”
};

class Student : private Person
{
public:
    Student(const string& str = "Student") : Person(str) {}
};

仍然是以普通人和学生作为例子,学生是private派生自普通人的。ID表示身份状态(我将它声明为public的,完全是为了方便,我仍然强烈建议声明为private)。

void PrintID(const Person& p)            //打印ID
{
    cout << p.ID << endl;
}

我有下面这样的调用:

Person p;
Student s;
PrintID(p);            //好的,没有问题
PrintID(s);            //编译出错

好的,我已经注释出来了。编译出错,出错信息是:error C2243: “类型转换”: 从“Student *”到“const Person &”的转换存在,但无法访问。是不是有点跟你想的不大一样呢?

分析:

很多时候,我们都默认public继承,public继承勾勒出一种is-a的关,就是派生类是一种基类,从这个语义上理解似乎从派生类转换为基类的自动的(引用或指针方式)。我这里说的是private继承,private继承勾勒出一种“根据某物实现出”的关系。假设D以private派生于B,意思是D可以根据B对象实现得到。编译器不会自动将派生类对象转换为基类对象。

另外一点疑问,你可能注意到了PrintID如果传入的是Student对象,似乎访问了私有成员ID(private继承),是不是这个原因导致编译错误呢?我们将其改为下面这样:

void PrintID(const Person& p)            //打印ID
{
    cout << "ID Infor" << endl;
//    cout << p.ID << endl;
}

同样编译错误。所以,要注意:派生类到基类的引用或指针转换一定“完美”存在,前提是public继承,不能是private或protected继承

说明:还要补充说明一下,在类的继承层次中public为主,private有时候在设计上可以提供一点“便利”,protected我暂时还不知道有什么好的用处。

 


2、关键字typename导致的编译时错误。

这个问题,可能很多人都知道,也许像我一样知道的“不全”。首先需要明确一点是:关键字typename与class在声明template类型参数时完全一样。我其实要说的是typename泛型(模板)中的使用,它可是大有所为的。

template内出现的名称如果依赖于某个template参数,称之为从属名称。如果从属名称在class内呈嵌套状,称之为嵌套从属名称。

template <typename T>
void PrintElement(const T& vec)
{
    T::const_iterator* x;            //const_iterator是个嵌套从属名称
...
}

我们可能认为声明了一个指针,指向一个T::const_iterator。实际上,T::const_iterator也可能是个名字为const_iterator的static变量,上面那行代码就定义了一个乘法。所以,嵌套从属名称可能导致解析困难

C++解析器有个简单的规则:在template中遇到一个嵌套从属名称,它会假设这个名称不是个类型,这是缺省情况,除非你用关键字typename主动告诉编译器。

template <typename T>
void PrintElement(const T& vec)
{
    typename T::const_iterator iter;            //这样声明就对了
...
}

这块知识,基本每个人都知道。我其实想说的是,使用typename时,有几个小点注意一下,因为typename的位置不仅在函数内。比如:

template <typename T>
void Print(typename T::const_iterator iter)        //形参表一定要使用typename

下面再举个复杂一点的例子:

template <class T>
class Base 
{
public:
    class Nested 
    {
    public:
        Nested(int a) : x(a) {}
    private:
        int x;
    };
};

template<class T>
class Derived : public Base<T>::Nested                    //继承列表,你不能使用typename
{
public:
    explicit Derived(int a) : Base<T>::Nested(a){}        //构造函数初始化列表,你不能使用typename
};

注意,代码中注释的部分,就是typename两个不能使用的地方。我再扩展一下这个问题:

template<class T>
void func(const typename Base<T>::Nested& test)            //使用typename,很好的
{
    cout << "Very Good" << endl;            
}

我想问的是:Derived<int> d(5);怎么调用那个函数呢?正确是策略是:

func<int> (d);                //正确的
func(d);                    //错误的

错误的用法,编译器会提醒你:无法解析T。

 


3、构造函数、析构函数能否为虚函数?构造函数、析构函数能否调用虚函数?

这个小问题,真是源远流长了。标准答案是不是都有了呢?我再不耐其烦的说一下。(我没有把各家说法收集全,就是说下自己的理解)

(1)构造函数、析构函数能否为虚函数?

构造函数不能为虚函数。

回答:有好几个点可以说说,我说的不全。

1、虚函数的作用主要是为了执行期根据Base指针寻址到“正确”的函数。既然是执行期,必然已经跨过编译期了,那势必对象都建立起来了,构造函数是初始化用的,那要构造函数何用。

2、如果我没记错的话,构造函数有一个功能是建立vptr,注意不是vtbl(编译期搞定的),假设是虚函数,谁来建立vptr,此时构造函数应该在vtbl里待着呢。

3、虚函数是为了派生类对其进行改造的,但构造函数不能被继承,谈何改造。

等等…

析构函数能不能为虚函数?

如果像STL那样坚决不做派生类,我劝你别把它当虚函数,vptr好歹也是4个字节的存储呢。如果你要它作为派生类,我劝你一定要设置为虚函数,否则内存泄露会找上你。

(2)构造函数、析构函数能否调用虚函数?

其实这个问题,我一直都没明白有啥意义。构造函数、析构函数调用虚函数是可以的,可是调用的是所属类的版本

 


目录:

C++中几个值得分析的小问题(1)

C++中几个值得分析的小问题(2)

posted @ 2015-04-29 22:17  QingLiXueShi  阅读(933)  评论(1编辑  收藏  举报