Skip to main content
GameDev.net gamedev.net
🔒 Locked

Why doesn't this work? (C++ multiple inheritance)

Started by legalize Jun 30, 2009 at 9:31 PM 6 replies 5.2k views
Original Post
legalize
legalize
OK, so I'm making fake objects for COM interfaces (ID3D11BlendState, etc.) and I'd like to coalesce the implementations of the base classes:

IUnknown
  ID3D11DeviceChild
    ID3D11BlendState
    ID3D11RasterizerState
so that I don't have to keep repeating the definitions of the members of ID3D11DeviceChild in the implementations of ID3D11BlendState. I thought this was what the virtual keyword was for when declaring class hierarchies, but apparently I'm doing something wrong. If I do this:

class FakeD3D11DeviceChild : public ID3D11DeviceChild
{
public:
	FakeD3D11DeviceChild()
	{
	}

	// IUnknown
	virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) { throw NotImplementedException(); }
	virtual ULONG STDMETHODCALLTYPE AddRef()	{ throw NotImplementedException(); }
	virtual ULONG STDMETHODCALLTYPE Release()	{ throw NotImplementedException(); }

	// ID3D11DeviceChild
	virtual void STDMETHODCALLTYPE GetDevice(ID3D11Device **ppDevice) { throw NotImplementedException(); }
	virtual HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) { throw NotImplementedException(); }
	virtual HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) { throw NotImplementedException(); }
	virtual HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) { throw NotImplementedException(); }
};


class FakeD3D11BlendState : public ID3D11BlendState, virtual public FakeD3D11DeviceChild
{
public:
	FakeD3D11BlendState() : FakeD3D11DeviceChild()
	{
	}

	// ID3D11BlendState
	virtual void STDMETHODCALLTYPE GetDesc(D3D11_BLEND_DESC *pDesc) { throw NotImplementedException(); }
};
Then when I try to instantiate FakeD3D11BlendState, I get errors like this:

1>d:\projects\d3d11helpers\testd3d11helpers\testblenddescription.cpp(36) : error C2259: 'FakeD3D11BlendState' : cannot instantiate abstract class
1>        due to following members:
1>        'HRESULT IUnknown::QueryInterface(const IID &,void **)' : is abstract
1>        c:\program files\microsoft sdks\windows\v6.0a\include\unknwn.h(116) : see declaration of 'IUnknown::QueryInterface'
1>        'ULONG IUnknown::AddRef(void)' : is abstract
1>        c:\program files\microsoft sdks\windows\v6.0a\include\unknwn.h(120) : see declaration of 'IUnknown::AddRef'
1>        'ULONG IUnknown::Release(void)' : is abstract
1>        c:\program files\microsoft sdks\windows\v6.0a\include\unknwn.h(122) : see declaration of 'IUnknown::Release'
1>        'void ID3D11DeviceChild::GetDevice(ID3D11Device **)' : is abstract
1>        c:\dxsdk\include\d3d11.h(1316) : see declaration of 'ID3D11DeviceChild::GetDevice'
1>        'HRESULT ID3D11DeviceChild::GetPrivateData(const GUID &,UINT *,void *)' : is abstract
1>        c:\dxsdk\include\d3d11.h(1320) : see declaration of 'ID3D11DeviceChild::GetPrivateData'
1>        'HRESULT ID3D11DeviceChild::SetPrivateData(const GUID &,UINT,const void *)' : is abstract
1>        c:\dxsdk\include\d3d11.h(1328) : see declaration of 'ID3D11DeviceChild::SetPrivateData'
1>        'HRESULT ID3D11DeviceChild::SetPrivateDataInterface(const GUID &,const IUnknown *)' : is abstract
1>        c:\dxsdk\include\d3d11.h(1336) : see declaration of 'ID3D11DeviceChild::SetPrivateDataInterface'
Obviously I'm not doing this right. I need multiple inheritance so I can declare that FakeD3D11BlendState derives from my base implementation and ID3D11BlendState. However, I don't want to have to repeat the implementation of the members in the base class. What am I missing here? I thought that applying virtual to the base implementation class was going to take care of it, but obviously not.
Ashkan
Ashkan
The virtual keyword must be used just below the top of the diamond, not at the join-class, i.e. FakeD3D11DeviceChild and ID3D11BlendState must both virtually inherit from ID3D11DeviceChild, which requires changing ID3D11BlendState's declaration. Composition can (and generally should) be used to the same effect. Factor the common behavior in one class and store an instance of it in other classes. Those classes can then delegate the job to that instance instead.
Peter_APIIT
Peter_APIIT
         A        /         B    C      \     /         D


Virtual keyword in B and C.
legalize
legalize
Hrm. OK. So how does ATL pull this off, then? I"ll have to consult my copy of ATL Internals to see if they say anything about this.

Sure, I can delegate to a contained member, but then I'm just shifting the duplication from the implementation to the delegation. Now I've got all this boiler plate delegation laying around.

Plus, since these are fake objects I'm creating for sensing with unit tests, the fakes have a much larger surface area than just the interface. They have a bunch of members for probing the sensed interaction with the system under test. That's a whole shitload of delegation, much, much larger than just the surface of the interface.
Ashkan
Ashkan
I've been in similar situations and I've done it both ways. Frankly, my impressions of virtual inheritance are not favorable. It's not intuitive (for instance, the order of constructors are also awkward besides the syntax), it's error-prone and it has a higher run-time cost, but perhaps the most irritating issue is that you can't use static_casts to downcast from a parent to one of its children. This is a real deal-breaker for me. In my eyes, the cost of writing the extra delegation is negligible compared to the headaches that virtual inheritance has caused me.
legalize
legalize
I think its going to be macros to eliminate the duplication, since its going to be the same for all derived classes. Ugh.
Sc4Freak
Sc4Freak
As I understand it, COM is designed only for interface inheritance, not implementation inheritance.

In COM, all the interfaces are, well, interfaces. They contain only pure virtual functions - which means that the diamond problem doesn't apply and multiple inheritance works just fine.

When it comes down to actually providing implementations for these interfaces (eg. in the internals of D3D), I have no idea what they do. But looking at the documentation Microsoft defines two methods of reusing COM implementations.

In your case, I think the best solution is to simply delegate to a contained member.
legalize
legalize
In my case I don't really care if I'm living up to the whole "COM contract", because I'm just creating fakes for my unit tests. These aren't real implementations that will be used in a production environment. Additionally, D3D just uses COM to manage object lifetimes, they don't really honor the full COM environment (you can't instantiate them with CoCreateInstance, for example). This is legal under COM (COM doesn't require than an object be CoCreateable, for instance, that's just one way of instantiating an object, albeit the most common one). However, because MS doesn't do full-blown COM on their objects, sometimes they get a little sloppy. They mess up the interface hierarchy, IUnknown doesn't always work right and the IIDs get duplicated sometimes. I file bugs on their headers when they do this, but they feel I'm being a pedantic PITA instead of finding genuine bugs.

At any rate, I've ended up with something like this for my base fake implementations:

#if !defined(FAKE_UNKNOWN_H)#define FAKE_UNKNOWN_H#include "NotImplementedException.h"class FakeIUnknown : public IUnknown{public:	FakeIUnknown() { }	// IUnknown	virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) { throw NotImplementedException(); }	virtual ULONG STDMETHODCALLTYPE AddRef()	{ throw NotImplementedException(); }	virtual ULONG STDMETHODCALLTYPE Release()	{ throw NotImplementedException(); }};#define CONTAIN_UNKNOWN(member_) \ 	FakeIUnknown member_; \ 	virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) { return member_.QueryInterface(riid, ppvObject); } \  	virtual ULONG STDMETHODCALLTYPE AddRef()	{ return member_.AddRef(); } \  	virtual ULONG STDMETHODCALLTYPE Release()	{ return member_.Release(); }#endif


(Hrm. Seems code blocks don't like lines ending in backslash!)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.