-
March 23rd, 2017, 01:39 PM
#1
a best-practices or industry-standard way to solve this diamond inheritance problem?
Hello,
I have discovered a problem in a much larger program and am wondering if someone could please tell me if there's a common, best-practices way to solve it.
Code:
class CObjectNeedingExternalResources;
class CSharedResourceReferenceCounter
{
public:
void AddResourceUser(CObjectNeedingExternalResources const *); // Computing the resources for a new object is time-intensive
void RemoveResourceUser(CObjectNeedingExternalResources const *);
};
class CBase
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p) { m_pObject = p; }
virtual CObjectNeedingExternalResources * GetObject() const { return m_pObject; }
private:
CObjectNeedingExternalResources * m_pObject;
};
class CASide : virtual public CBase
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p)
{
if (GetObject() != p)
{
m_singleton.RemoveResourceUser(GetObject());
CBase::SetObject(p);
m_singleton.AddResourceUser(p);
}
}
private:
static CSharedResourceReferenceCounter m_singleton;
};
class CBSide : virtual public CBase
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p)
{
if (GetObject() != p)
{
// ******* This block of code doesn't get executed when CDiamond::SetObject is called.
CBase::SetObject(p);
DoTimeExpensiveComputation(p);
}
}
};
class CDiamond : virtual public CASide, virtual public CBSide
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p)
{
if (GetObject() != p)
{
CASide::SetObject(p);
CBSide::SetObject(p);
DoOtherTimeExpensiveComputation(p);
}
}
};
The code which uses an instance of CDiamond has to call SetObject thousands of times, but the argued pointer will only change a few dozen times in all those thousands of calls. So the various SetObject functions need to protect against wasting time. The way CDiamond is used is rather complicated and the various objects which use it cannot themselves avoid making the redundant calls.
As indicated above, the problem is that the body of the CBSide::SetObject if-statement doesn't get execuated because CASide::SetObject already updated the base class pointer.
Is there a common way people usually solve this problem? If so, could some guru please enlighten me?
If not I'm wondering about two possible solutions as alternatives. First, C++ forces the most derived class to construct everything, so one way to solve the problem would be to mimic that:
Code:
class CBase
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p) { SetObjectHelper(p, p); }
virtual CObjectNeedingExternalResources * GetObject() const { return m_pObject; }
protected:
void SetObjectHelper(CObjectNeedingExternalResources * pOld,
CObjectNeedingExternalResources * pNew)
{
UNREFERENCED_PARAMETER(pOld);
m_pObject = pNew;
}
private:
CObjectNeedingExternalResources * m_pObject;
};
class CASide : virtual public CBase
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p)
{
CObjectNeedingExternalResources * pOld = GetObject();
if (pOld != p)
{
CBase ::SetObjectHelper(pOld, p);
CASide::SetObjectHelper(pOld, p);
}
}
protected:
void SetObjectHelper(CObjectNeedingExternalResources * pOld,
CObjectNeedingExternalResources * pNew)
{
m_singleton.RemoveResourceUser(pOld);
m_singleton.AddResourceUser (pNew);
}
private:
static CSharedResourceReferenceCounter m_singleton;
};
class CBSide : virtual public CBase
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p)
{
CObjectNeedingExternalResources * pOld = GetObject();
if (pOld != p)
{
CBase ::SetObjectHelper(pOld, p);
CBSide::SetObjectHelper(pOld, p);
}
}
protected:
void SetObjectHelper(CObjectNeedingExternalResources * pOld,
CObjectNeedingExternalResources * pNew)
{
UNREFERENCED_PARAMETER (pOld);
DoTimeExpensiveComputation(pNew);
}
};
class CDiamond : virtual public CASide, virtual public CBSide
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p)
{
CObjectNeedingExternalResources * pOld = GetObject();
if (pOld != p)
{
CBase ::SetObjectHelper(pOld, p);
CASide ::SetObjectHelper(pOld, p);
CBSide ::SetObjectHelper(pOld, p);
CDiamond::SetObjectHelper(pOld, p);
}
}
protected:
void SetObjectHelper(CObjectNeedingExternalResources * pOld,
CObjectNeedingExternalResources * pNew)
{
UNREFERENCED_PARAMETER (pOld);
DoOtherTimeExpensiveComputation(pNew);
}
};
A second solution I'm wondering about which might be cleaner is to let all the derived classes have their own copy of the triggering variable:
Code:
class CBase
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p) { m_pObject = p; }
virtual CObjectNeedingExternalResources * GetObject() const { return m_pObject; }
private:
CObjectNeedingExternalResources * m_pObject;
};
class CASide : virtual public CBase
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p)
{
if (m_pObjectASide != p)
{
m_singleton.RemoveResourceUser(GetObject());
CBase::SetObject(p);
m_singleton.AddResourceUser(p);
m_pObjectASide = p;
}
}
private:
static CSharedResourceReferenceCounter m_singleton;
CObjectNeedingExternalResources * m_pObjectASide;
};
class CBSide : virtual public CBase
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p)
{
if (m_pObjectBSide != p)
{
// ******* This block of code doesn't get executed when CDiamond::SetObject is called.
CBase::SetObject(p);
DoTimeExpensiveComputation(p);
m_pObjectBSide = p;
}
}
private:
CObjectNeedingExternalResources * m_pObjectBSide;
};
class CDiamond : virtual public CASide, virtual public CBSide
{
public:
virtual void SetObject(CObjectNeedingExternalResources * p)
{
if (m_pObjectDiamond != p)
{
CASide::SetObject(p);
CBSide::SetObject(p);
DoOtherTimeExpensiveComputation(p);
m_pObjectDiamond = p;
}
}
private:
CObjectNeedingExternalResources * m_pObjectDiamond;
};
Is one of these two solutions better than the other?
Thank you!
GeoRanger
Last edited by GeoRanger; March 23rd, 2017 at 04:14 PM.
-
March 23rd, 2017, 05:43 PM
#2
Re: a best-practices or industry-standard way to solve this diamond inheritance probl
Originally Posted by GeoRanger
Hello,
I have discovered a problem in a much larger program and am wondering if someone could please tell me if there's a common, best-practices way to solve it.
The second most important rule of object oriented programming (OO) is to favor object composition over class inheritance.
What would happen if CDiamond didn't inherit CASide and CBSide but instead held an object of each, and CASide and CBSide didn't inherit CBase but instead each held a CBase object?
Last edited by wolle; March 24th, 2017 at 03:26 AM.
-
March 24th, 2017, 03:49 AM
#3
Re: a best-practices or industry-standard way to solve this diamond inheritance probl
as it is, CBase does what it promises, so I'd improve CBase semantics, rather than hack it in subclasses
now, as far as I can tell, CBase responsability is to manage a dynamic member and signal when a change occurred to subclasses.
So, why not something like ?
Code:
class CBase
{
public:
void SetObject(CObjectNeedingExternalResources * p) { if( m_pObject != p ) { OnObjectChange( m_pObject, p ); m_pObject = p; } } // no need to be virtual
CObjectNeedingExternalResources * GetObject() const { return m_pObject; } // no need to be virtual
protected:
virtual void OnObjectChange( CObjectNeedingExternalResources * Oldp, CObjectNeedingExternalResources * Newp ){} // do nothing by default, guarantees Oldp != Newp
private:
CObjectNeedingExternalResources * m_pObject = nullptr;
};
class CASide : virtual public CBase
{
protected:
virtual void OnObjectChange( CObjectNeedingExternalResources * Oldp, CObjectNeedingExternalResources * Newp )
{
m_singleton.RemoveResourceUser(Oldp);
m_singleton.AddResourceUser(Newp);
}
private:
static CSharedResourceReferenceCounter m_singleton;
};
class CBSide : virtual public CBase
{
protected:
virtual void OnObjectChange( CObjectNeedingExternalResources * Oldp, CObjectNeedingExternalResources * Newp )
{
DoTimeExpensiveComputation(Newp);
}
};
class CDiamond : virtual public CASide, virtual public CBSide
{
protected:
virtual void OnObjectChange( CObjectNeedingExternalResources * Oldp, CObjectNeedingExternalResources * Newp )
{
CASide::OnObjectChange( Oldp, Newp );
CBSide::OnObjectChange( Oldp, Newp );
DoOtherTimeExpensiveComputation(Newp);
}
};
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|