Domanda

Hi i have a question regarding how to access parts of inherited code.

Say i have this WorldObject that is a base class for alot of other objects. Then i have a class Chest that inherit from WorldObject and also from the abstract class OpenAble, with some methods like open and unlock.

In my main i have a vector of WorldObjects that i iterate through with a for loop. Now to the question, how can i check if a worldobject is also of OpenAble and how can i access the methods in OpenAble.

class WorldObject
{
...     //implementation
};

class OpenAble
{
public:
    OpenAble(){}
    virtual ~OpenAble(){}
    virtual void Open() = 0;
    virtual void Unlock(int k) = 0;
};

class Chest : public WorldObject, public OpenAble
{
...  //implementation
};

main()
{
std::vector<WorldObject> objVector;     //vector with several Worldobjects

for (int i =0; i < objVector.Size(); i++)
{
//check if a WorldObject is also of openable
//Do som actions like, open or unlock
//How?
}
};
È stato utile?

Soluzione

You could do a dynamic_cast<OpenAble>. This will throw an error if it is the wrong type though which is relatively expensive given that it is quite likely that the object will be the wrong type.

try{
  OpenAble& opener = dynamic_cast<OpenAble&>(worldObj);
} catch (std::bad_cast& ex){
  //not openable
}

BTW: As pointed out in the comments below, if you use a pointer to the base class in your container instead of references, then you can (and should) use the pointer version of dynamic_cast which will return a null in the case that your object is not OpenAble. Checking that in your case would be a lot more efficient than throwing and catching exceptions.

I would recommend an entirely different approach though. Inject your base class with an "OpenPolicy".

E.g.

class CanOpenPolicy {
public:
  boolean canOpen(){ return true; };
  boolean canClose(){ return true; };
  boolean isOpen(){ return openState; };
  void open(){ openState = OPEN; };
  void close(){ openState = CLOSED; };
}

class NoOpenPolicy {
public:
  boolean canOpen(){ return false; };
  boolean canClose(){ return false; };
  boolean isOpen(){ return CLOSED; };
  void open(){ throw IllegalWorldObjectAction("OpenPolicy disallows operation"); };
  void close(){ throw IllegalWorldObjectAction("OpenPolicy disallows operation"); };
}

//injection via template (no need for base "OpenPolicy" class, maybe some
// obscure error codes at compile though)
// Implicit interface based on how you use the injected policy.
template<OpenPol>
class WorldObject {
private: 
  // CTOR part of the injected contract so you are not tied to knowing how to 
  // build the policy. This is a key benefit over interface based injection.
  OpenPol openPol; 
  ...
public:
  ...
  void open(){
    if(openPol.canOpen()){
      openPol.open();
    }
  }
  ...
}

That's not tested or anything. Just to illustrate the idea. You can add multiple policies for different possible operations and the best thing is that you won't need a lot of hierarchies.

To use it just do something like this:

std::unique_ptr<WorldObject>( new Chest() );
std::unique_ptr<WorldObject>( new Banana() );
std::unique_ptr<WorldObject>( new Chair() );

where:

class Chest : public WorldObject<CanOpenPolicy> {
   // Very little implementation in here.
   // Most of it is handled in the base class and the injected policies :)
}
class Banana: public WorldObject<CanOpenPolicy> {
}
class Chair : public WorldObject<NoOpenPolicy> {
}

Altri suggerimenti

The most important thing, even though you may not like this, is to not throw away type information in the first place.

Collections of generic "object" is a Java'ism, it's not how to do things in C++.

That said, provided the statically known class is polymorphic (has at least one virtual member function), you can use dynamic_cast or typeid. This functionality is known as RTTI, short for Run Time Type Information. With some compilers you have to use special options to enable RTTI.

Idiomatic use of dynamic_cast:

WorldObject* p = ...;
if( auto p_openable = dynamic_cast<OpenAble*>( p ) )
{
    // use p_openable
}

Note that dynamic_cast to pointer signals failure by returning a nullpointer, while dynamic_cast to reference signals failure by throwing an exception, since there are no nullreferences.

The simple (obvious) solution is to use dynamic_cast and cast your objects to OpenAble.

The problem with "the simple (obvious) solution" is that usually, use of dynamic_cast shows a lack of flexibility in your class hierarchy and is a symptom of a design problem.

I would offer the OpenAble interface as a set of behavior exposed through a handle:

class OpenAble { /* ... */ };

class WorldObject
{
    //implementation
    virtual OpenAble* GetOpener() { return nullptr; }
};

class Chest: public WorldObject {
    struct ChestOpener: public OpenAble {
         Chest *c;
         virtual void Open() {
             // do stuff with c
         }
    };
    std::unique_ptr<OpenAble> chest_opener;
public:
    virtual OpenAble* GetOpener() {
        if(!chest_opener) {
             chest_opener = new ChestOpener{ this };
        }
        return chest_opener.get();
    }
};

Client code:

std::vector<WorldObject> objVector;     //vector with several Worldobjects

for(auto &obj: objVector)
{
    if(auto openerHandle = obj.GetOpener())
        openerHandle->Open();
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top