0

I'm currently working on a C++ project, I'm dealing with a home-made class CSound and handling its instances with a CSoundEngine, which allow myself to create a CSound (stored in my CSoundEngine) and return a pointer to it.

The fact is that I would like, for design purposes, to return a pointer to an interface. It is possible to store a real object in a vector for example, and serve it via an interface in C++ (or Java) ?

If the answer is yes, is it possible to store a vector a generic object which can be extended and return a specific interface depending on the child class ?

Thanks !

user3240711
  • 31
  • 1
  • 3
  • Why are you asking about two distinct languages? That makes your question too broad. Please remove the one that you are least interested in. – Stephen C May 16 '17 at 09:25
  • I don't understand the second part of your question. Please clarify. – Stephen C May 16 '17 at 09:27
  • This question looks kinda like jeopardy... Are you asking what abstract base classes are used for? It would be better if you drafted some code and show what you are trying to do in a meaningful example. – luk32 May 16 '17 at 09:31
  • It is possible to store somewhere (in a vector for example), a bunch generic objects which can be extended (by an inherited child class) and return a interface to this specific object ? – user3240711 May 16 '17 at 09:33
  • @user3240711 yes, that's possible; C++ does have virtual dispatching and inheritance-based polymorphism just like Java. – Quentin May 16 '17 at 10:02

1 Answers1

0

So, if I understand you correctly, you have something like:

class CSound {
    // Whatever
}; 

class CSoundEngine {
    // A lot of stuff
    CSound* createSound(/* ... */);
};

And you don't want CSound be returned, but an interface, ALTHOUGH it is internally stored in a vector with it's specific type?

Well the first problem - Interface - is easy to solve:

Create your interfaces and derive from them.

class ISound {
    public:
        ISound(const ISound&)             = delete;
        ISound(ISound&&)                  = delete;
        ISound& operator =(const ISound&) = delete;
        ISound& operator =(ISound&&)      = delete;

        virtual ~ISound() = default;

        // Your ISound public API here as pure virtual methods, e.g.:
        virtual const std::string name() const = 0;
    protected:
        ISound() = default;
};

class ILoopable {
    public:
        ILoopable(const ILoopable&)             = delete;
        ILoopable(ILoopable&&)                  = delete;
        ILoopable& operator =(const ILoopable&) = delete;
        ILoopable& operator =(ILoopable&&)      = delete;

        virtual ~ILoopable() = default;

        // Your ILoopable public API here as pure virtual methods, e.g.:
        virtual const bool isLoopingActive()      const = 0;
        virtual       bool setLoopingActive(bool)       = 0;
    protected:
        ILoopable() = default;
};

class CDefaultSound 
    : public ISound {
    public:
        CDefaultSound () = default;

       // ISound implementation
        inline const std::string name() const { return mName; }

    private:
        std::string mName;
};


class CLoopableSound 
    : public ISound,
      public ILoopable {
    public:
        CLoopableSound() 
            : ISound(), 
              ILoopable(),
              mLoopingActive(true),
              mName("")
        {
        }


       // ISound implementation
        inline const std::string name() const { return (mName + "(Loopable)"); }

       // ILoopable implementation
        inline const bool isLoopingActive() const { return mLoopingActive; }
        inline bool setLoopingActive(bool active) { mLoopingActive = active; }

    private:
        bool        mLoopingActive;
        std::string mName;
};

int main()
{
    // Now you can do something like this, for example, using polymorphism
    // in your CSoundEngine (see below)...
    ISound *pDef      = new CDefaultSound();
    ISound *pLoopable = new CLoopableSound();
}

If you want to use only CSound deriving from ISound that's fine, you don't need multiple classes, but then I don't understand the point of using an interface. Important: Due to the pure virtual interface methods, you cannot instaniate the interface class, so you have to use a pointer or RAII-pointer like shared_ptr or unique_ptr (I'd recommend RAII anyway...)

The second problem - storing specific type in vector - is much harder, since you'd required a single vector foreach permitted type. OR! You store the interface-instances and only use the interface-methods!

class DefaultSoundCreator {
    static ISound* createSound(/* Criteria */) { ... }
};

template <typename TSoundCreator>
class CSoundEngine {
    public:
        CSoundEngine() 
            : mSoundCreator() { 
        }

        std::shared_ptr<ISound> createSound(/* some criteria */);

    private:
        std::vector<std::shared_ptr<ISound>> mSounds;
};

// cpp
std::shared_ptr<ISound> CSoundEngine::createSound(/* some criteria */) {
   // Use criteria to create specific sound classes and store them in the mSOunds vector.
   ISound *pSound = TSoundCreator::createSound(/* forward criteria for creation */);
   std::shared_ptr<ISound> pSoundPtr = std::shared_ptr<ISound>(pSound);

   mSounds.push_back(pSoundPtr);

   return pSoundPtr;
}

int main() {
   std::unique_ptr<CSoundEngine<DefaultSoundCreator>> pEngine = std::make_shared<CSoundEngine<DefaultSoundCreator>>();

   std::shared_ptr<ISound> pSound = pEngine->createSound(/* Criteria */);
}

This way you could rely on the functionality provided by ISound, but by specifying the Creator-class, you could control sound-creation with a generic sound engine.

Now the actual problem of type-erasure using interface base classes: You known you stored a CLoopableSound at index 2 but you can only use ISound-Interfacemethods, due to the sound-engine's method createSound() : std::shared_ptr;

How do you access the ILoopable-behaviour?

And this is the point, where it becomes philosophical... I would recommend reading:

https://akrzemi1.wordpress.com/2013/11/18/type-erasure-part-i/ Type erasure techniques https://aherrmann.github.io/programming/2014/10/19/type-erasure-with-merged-concepts/

One technique I like to use:

 class CLoopableSound
     : public ISound {
    // All the above declarations and definitions
    // AND:
    static std::shared_ptr<CLoopableSound> fromISound(const std::shared_ptr<ISound>& other, bool *pSuccess = nullptr) {
        std::shared_ptr<CLoopableSound> p = std::static_pointer_cast<CLoopableSound>(other);
        if(pSuccess)
            *pSuccess = p.operator bool();

        return p;
    }
 };

 // Use like
 std::shared_ptr<CLoopableSound> pLoopable = CLoopableSound::fromISound(pSoundEngine->getSound(...));
 if(pLoopable) {
     // Use
 }

Finally, you could of course make the fromISound-function a template and use the casts to access only ILoopable instead of CLoopableSound, etc..

Community
  • 1
  • 1
MABVT
  • 1,350
  • 10
  • 17