CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 3 of 3
  1. #1
    Join Date
    Mar 2006
    Posts
    151

    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.

  2. #2
    Join Date
    Feb 2017
    Posts
    677

    Re: a best-practices or industry-standard way to solve this diamond inheritance probl

    Quote Originally Posted by GeoRanger View Post
    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.

  3. #3
    Join Date
    Oct 2008
    Posts
    1,456

    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
  •  





Click Here to Expand Forum to Full Width

Featured