首页
社区
课程
招聘
[讨论]基类的方法为什么不能直接被派生类当接口使用?
发表于: 2014-9-9 17:53 9990

[讨论]基类的方法为什么不能直接被派生类当接口使用?

2014-9-9 17:53
9990
技术探讨,先讲清现象,大家不要纠结为什么这样写
1.有一些基类实现了大量的基本功能,或为了减少代码,才有基类这种设计
如:
class A
{
public:
   Addref();
   Release();
}
2.新写一个派生类,实现各种接口:
class B : public A, public IUnknown
{
public:
    QueryInterface(xxxx);
}
这时候是编译不过的,提示说class B没有实现Addref和Release
我一直就不理解,类B从A继承,理论上B已经有了所有的方法啊
这些方法完全可以用来充当IUnknow的方法啊
结果编译器硬是不拿 来用,而是说没有实现

这是啥情况?

我重新在B里实现方法是可以编译过的,但当代码重用性非常多的时候,明显是不合理的

详细代码示例(大家可用下面的代码直接放在cpp里编译):
#include "stdafx.h"
#include <windows.h>

class A
{
public:
	virtual ULONG STDMETHODCALLTYPE AddRef(void)
	{
		return 0;
	}

	virtual ULONG STDMETHODCALLTYPE Release(void)
	{
		return 0;
	}
};

class B : public A, public IUnknown
{
public:
	virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
	{
		return S_OK;
	}

	void Test()
	{
		void* p = new B();
	}
};


编译错误:
1>ClCompile:
1>  Test.cpp
1>test.cpp(31): error C2259: “B”: 不能实例化抽象类
1>          由于下列成员:
1>          “ULONG IUnknown::AddRef(void)”: 是抽象的
1>          c:\program files (x86)\microsoft sdks\windows\v7.0a\include\unknwn.h(120) : 参见“IUnknown::AddRef”的声明
1>          “ULONG IUnknown::Release(void)”: 是抽象的
1>          c:\program files (x86)\microsoft sdks\windows\v7.0a\include\unknwn.h(122) : 参见“IUnknown::Release”的声明
1>
1>生成失败。

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (27)
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
微软中有大量COM接口是派生自某些接口的,我在实现他们时,如果用基类和派生类的话,就有上述情况
2014-9-9 18:33
0
雪    币: 0
活跃值: (26)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
class 的成员好像默认是private的,既然是public继承的,就改成:
class A{
            public:
            Addref();
            Release();
};
详情百度  C++访问修饰符
2014-9-9 19:29
0
雪    币: 19
活跃值: (74)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
LS说的对,struct默认是public,class默认是private,在子类和外部都不可以访问private的。
2014-9-9 19:41
0
雪    币: 150
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
3楼应该是正解,好久不写C++了.都快忘光了.应该是访问修饰符的问题.
2014-9-9 21:29
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
不好意思,我写示例的情况下没注意到,其实我全是public的
所以让楼下几位理解错误了

重新考虑下:
所有基类的public方法,并不能让派生类用来实现接口的方法
2014-9-10 09:32
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
看我楼上的方法,public下也是不行的
2014-9-10 09:33
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
8
看我楼上的方法,public下也是不行的
2014-9-10 09:45
0
雪    币: 200
活跃值: (525)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
能否把错误信息贴上来?错误的原因会很多种
2014-9-10 12:47
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
1楼已更新,代码可以直接编译看结果,1楼也把错误信息放上去了
2014-9-10 15:00
0
雪    币: 134
活跃值: (11)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
11
错误信息里说的不是 IUnknown里的方法么?
2014-9-10 15:41
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
12
是啊,class B要实现IUnknow啊
但只实现了QueryInterface一个方法,ADDref和Release想借用class A的啊,不成功嘛
2014-9-10 16:07
0
雪    币: 185
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
楼主肯定没看到“抽象” 两个字是什么意思。

C++ 里面有个东西叫纯虚函数。  也就是funtion() = 0; 这种。

父类根本就没有实现 ,只是一个接口而已,你不实现当然就过不了。

这种类是不能实例化的。
2014-9-10 16:17
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
大哥,你看下class A行不,里面是实现了的啊,IUnknown里是纯虚函数,但IUnkown里的方法class A里有啊,既然B从A继承,就可想而知B也有这些方法,编译器可以用这些函数来填充虚表啊

貌似你就没注意class A的存在
所以我的贴子标题的主要思想就是借用父类的方法来完成派生类的接口
2014-9-10 16:31
0
雪    币: 134
活跃值: (11)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
15
哦哦,明白你的意思了,你是在A中有接口里的同名方法,然后你就想用B来继承A 利用A中的同名方法作为接口方法的实现。。。
那你就要override接口中的AddRef方法,然后调用base.AddRef(); //C#中的写法...C++不熟
2014-9-10 16:33
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
16
是啊,我是这意思
但我就是不想再写一次addref,麻烦啊,工程大的时候,数百个方法重写,费时费力

既然class B是从class A继承的,理论上B自身也就有addref这个方法啦,为啥编译器不认为他有了?
2014-9-10 17:21
0
雪    币: 3502
活跃值: (1493)
能力值: ( LV15,RANK:1057 )
在线值:
发帖
回帖
粉丝
17
c++又没有反射,根本不是这样看函数名的,不在一条继承链上,A:AddRef和继承自IUnknow根本不是一个东西
2014-9-10 17:34
0
雪    币: 3502
活跃值: (1493)
能力值: ( LV15,RANK:1057 )
在线值:
发帖
回帖
粉丝
18
你写模板吧
template<typename T>
class A:public T
{
   public:
   add
   release
}

class B:public A<IUnkown>
2014-9-10 17:39
0
雪    币: 1787
活跃值: (340)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
19
这个解释差不多了。A 和 IUnknow 是平级的。看不到A的实现。直接派生虚接口的类,需要实现虚接口的函数。

下面这么写没问题

class A : public IUnknown
{
public:
        ULONG STDMETHODCALLTYPE AddRef(void)
        {
                return 0;
        }

        ULONG STDMETHODCALLTYPE Release(void)
        {
                return 0;
        }
};

class B : public A
{
public:
        HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
        {
                return S_OK;
        }

        virtual void Test()
        {
                void* p = new B();
        }
};
2014-9-10 18:15
0
雪    币: 185
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
我明显有看到啊。  你这种逻辑是不行的。  class A 的函数并不是继承至IUnknown::AddRef  。

根本就不是一个函数。 怎么可能去覆盖。   

这种只是从两个基类处同时继承了两个同名函数而已。 其中一个会覆盖掉另外一个。 你自己写写代码测试测试就知道了。
2014-9-10 20:32
0
雪    币: 61
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
你好像没有搞清楚多继承的关系,而且也分不清class的方法(他们可以理解为是在不同的命名空间里大概)。
2014-9-10 20:41
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
22
嗯,这方法不错,把所有接口声明在基类里,但又不实现,这样派生类再实现,就没我碰到的这个问题了,我的具体代码非常复杂,因为接口太多,方法太多,实在不好处理

不过你这写法也有一个问题,就是class A自身又不能实例化了(因为有些方法是放在class B里完成的)
2014-9-11 09:47
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
23
不同命名空间又不是一定有问题,如楼上的,让A 继承IUnknown,但不实现QueryInterface方法,由B来实现QueryInterface方法,这时QueryInterface明显命名空间与class A中的Release方法也不一样,不也能正确吗,是吧?

表现结果就是:
派生类可以用自己的方法填充基类的接口
派生类不可以用基类的方法填充自己的接口
2014-9-11 09:51
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
24
我的理解不一样
我的看法是这样的,虽说都是继承,但B继承IUnkown的是一个纯虚方法,为了编译过,编译器要求B必须实现此方法就好了

而刚好B又从A那里继承IUnkown中的同名同参函数,而且该函数是实现了的函数,我的看法主要是认为,编译器可以借用此函数来填充虚表,运行时应该不会出任何错误这就够了

主要观点我是对虚表的理解就是一个函数指针数组而已,只要将它们填上正确的函数,就没啥问题了

在我看来,编译器完全可以用A的方法来填写IUnknow的虚表而不出错,这就够了,可以省多少代码不是?省时省力,结果正确,这才是有意义的
2014-9-11 09:58
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
25
如果所有派生类为了实现接口,而要去重写方法进行调用,感觉除了浪费资源,还有啥意义吗?
当然,我可能是在牵扯编译器实现或c++的标准,这点我不能肯定,

但我认为,如果能直接借用,能起到节省的目的,是好事啊,不是吗?

看看c99,里面有些东西,真的省事好多,何乐而不为呢?
所谓的标准也是人制定的,只要有好处,做就是了 :)
2014-9-11 10:03
0
游客
登录 | 注册 方可回帖
返回
//