开发者

C++ checking the type of reference

Is it bad design to check if an object is of a particular type by having some sort of ID data member in it?

class A
{

private:
  bool isStub;
public:
 A(bool isStubVal):isStub(isStubVal){}
 bool isStub(){return isStub;}


};

class A1:public A
{
 public:
 A1():A(false){}
};

class AStub:public A
{
 public:
 AStub():A(true){}
};

EDIT 1: Problem is A holds a lot of virtual functions, which A1 doesn't override but the stub needs to, for indidicating that you are working on a stub instead of an actual object. Here maintainability is the question, for every function that i add to A, i need to override it in stub. forgetting it means dangerous behaviour as A's virtual function gets executed with stub's data. Sure I can add an abstract class ABase and let A and Astub inherit from them. But the design has become rigid enough to allow this refactor. A reference holder to A is held in another class B. B is init开发者_如何学Pythonialized with the stub reference, but later depending on some conditions, the reference holder in B is reinitialized with the A1,A2 etc.. So when i do this BObj.GetA(), i can check in GetA() if the refholder is holding a stub and then give an error in that case. Not doing that check means, i would have to override all functions of A in AStub with the appropriate error conditions.


Generally, yes. You're half OO, half procedural.

What are you going to do once you determine the object type? You probably should put that behavior in the object itself (perhaps in a virtual function), and have different derived classes implement that behavior differently. Then you have no reason to check the object type at all.

In your specific example you have a "stub" class. Instead of doing...

if(!stub) 
{ 
    dosomething; 
} 

Just call

object->DoSomething();

and have the implemention in AStub be a empty


Generally yes. Usually you want not to query the object, but to expect it to BEHAVE the proper way. What you suggest is basically a primitive RTTI, and this is generally frowned upon, unless there are better options.

The OO way would be to Stub the functionality, not check for it. However, in the case of a lot of functions to "stub" this may not seem optimal.

Hence, this depends on what you want the class to really do.

Also note, that in this case you don't waste space:

class A
{
  public:
    virtual bool isStub() = 0;
};

class A1:public A
{
  public:
    virtual bool isStub() { return false; };
};

class AStub:public A
{
  public:
    virtual bool isStub() { return true; };
};

... buuut you have a virtual function -- what usually is not a problem, unless it's a performance bottleneck.


If you want to find out the type of object at runtime you can use a dynamic_cast. You must have a pointer or reference to the object, and then check the result of the dynamic_cast. If it is not NULL, then the object is the correct type.


With polymorphic classes you can use the typeofoperator to perform RTTI. Most of the time you shouldn't need to. Without polymorphism, there's no language facility to do so, but you should need to even less often.


One caveat. Obviously your type is going to be determined at construction time. If your determination of 'type' is a dynamic quantity you can't solve this problem with the C++ type system. In that case you need to have some function. But in this case it is better to use the overridable/dynamic behavior as Terry suggested.

Can you provide some better information as what you are trying to accomplish?


This sort of thing is fine. It's generally better to put functionality in the object, so that there's no need to switch on type -- this makes the calling code simpler and localises future changes -- but there's a lot to be said for being able to check the types.

There will always be exceptions to the general case, even with the best will in the world, and being able to quickly check for the odd specific case can make the difference between having something fixed by one change in one place, a quick project-specific hack in the project-specific code, and having to make more invasive, wide-reaching changes (extra functions in the base class at the very least) -- possibly pushing project-specific concerns into shared or framework code.

For a quick solution to the problem, use dynamic_cast. As others have noted, this lets one check that an object is of a given type -- or a type derived from that (an improvement over the straightforward "check IDs" approach). For example:

bool IsStub( const A &a ) {
    return bool( dynamic_cast< const AStub * >( &a ) );
}

This requires no setup, and without any effort on one's part the results will be correct. It is also template-friendly in a very straightforward and obvious manner.

Two other approaches may also suit.

If the set of derived types is fixed, or there are a set of derived types that get commonly used, one might have some functions on the base class that will perform the cast. The base class implementations return NULL:

class A {
    virtual AStub *AsStub() { return NULL; }
    virtual OtherDerivedClass *AsOtherDerivedClass() { return NULL; }
};

Then override as appropriate, for example:

class AStub : public A {
    AStub *AsStub() { return this; }
};

Again, this allows one to have objects of a derived type treated as if they were their base type -- or not, if that would be preferable. A further advantage of this is that one need not necessarily return this, but could return a pointer to some other object (a member variable perhaps). This allows a given derived class to provide multiple views of itself, or perhaps change its role at runtime.

This approach is not especially template friendly, though. It would require a bit of work, with the result either being a bit more verbose or using constructs with which not everybody is familiar.

Another approach is to reify the object type. Have an actual object that represents the type, that can be retrieved by both a virtual function and a static function. For simple type checking, this is not much better than dynamic_cast, but the cost is more predictable across a wide range of compilers, and the opportunities for storing useful data (proper class name, reflection information, navigable class hierarchy information, etc.) are much greater.

This requires a bit of infrastructure (a couple of macros, at least) to make it easy to add the virtual functions and maintain the hierarchy data, but it provides good results. Even if this is only used to store class names that are guaranteed to be useful, and to check for types, it'll pay for itself.

With all this in place, checking for a particular type of object might then go something like this example:

bool IsStub( const A &a ) {
    return a.GetObjectType().IsDerivedFrom( AStub::GetClassType() );
}

(IsDerivedFrom might be table-driven, or it could simply loop through the hierarchy data. Either of these may or may not be more efficient than dynamic_cast, but the approximate runtime cost is at least predictable.)

As with dynamic_cast, this approach is also obviously amenable to automation with templates.


In the general case it might not be a good design, but in some specific cases it is a reasonable design choice to provide an isStub() method for the use of a specific client that would otherwise need to use RTTI. One such case is lazy loading:

class LoadingProxy : IInterface
{
private:
    IInterface m_delegate;

    IInterface loadDelegate();

public:
    LoadingProxy(IInterface delegate) : m_delegate(delegate){}

    int useMe()
    {
        if (m_delegate.isStub())
        {
            m_delegate = loadDelegate();
        }

        return m_delegate.useMe();
    }
};

The problem with RTTI is that it is relatively expensive (slow) compared with a virtual method call, so that if your useMe() function is simple/quick, RTTI determines the performance. On one application that I worked on, using RTTI tests to determine if lazy loading was needed was one of the performance bottlenecks identified by profiling.

However, as many other answers have said, the application code should not need to worry about whether it has a stub or a usable instance. The test should be in one place/layer in the application. Unless you might need multiple LoadingProxy implementations there might be a case for making isStub() a friend function.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜