简体   繁体   中英

Function with derived arguments in C++

Let's say I have the following classes:

class A {
};

class B : public A {
};

class C : public A {
};

class BaseClass {
public:
    virtual void dummy(A* a) = 0;
};

class DerivedClass1 : public BaseClass {
public:
    void dummy(B* b);
};

class DerivedClass2 : public BaseClass {
public:
    void dummy(C* c);
};

As you see B and C inherit from A and DerivedClass1 and DerivedClass2 inherit from BaseClass .

Due to my understanding this kind of declaration of function dummy() is not allowed, since the arguments have changed, even though B and C are derived from A .

What is the correct way to do it?

A possibility would be to leave the arguments as they're used to be in the BaseClass and just do casting afterwards within the dummy function, but I guess this isn't really nice.

Otherwise, just do no derivation of dummy (and remove it from BaseClass ), so both classes DerivedClass1 and DerivedClass2 declare them independently. But then the idea of all classes derived from BaseClass have to implement dummy goes lost.

First of all, when declaring that something A is a base class of B or C , you do so with the intent that B and C have all the properties of A and that you can use B and C in any place that you can use A in. A good design often provides a meaningful definition of A that allows for a generic definition of dummy for all specializations of A . In that sense, the problem might be less common than you may expect.

If you want to instantiate DerivedClass1 or DerivedClass2 , your notion of DerivedClass1 and DerivedClass2 does not really make sense, because in order to be a BaseClass , they have to provide a meaningful definition of void dummy(A *a) . You have to provide that definition. They may provide additional methods void dummy(B *b) or void dummy(C *c) , but those are only used if the static type of the BaseClass implementation is either DerivedClass1 or DerivedClass2 .

If DerivedClass1 or DerivedClass2 need to distinguish between B and C , this might be a sign of bad design. But it might be a well-informed decision as well. Generally, you need a way to dynamically dispatch to the right method. For example, this could be achieved like this:

class DerivedClass1 : public BaseClass
{
    virtual void dummy( A * a ) override {
        if( dynamic_cast<B*>( a ) != nullptr ) {
            dummy( dynamic_cast<B*>( a );
        }
        else if( dynamic_cast<C*>( a ) != nullptr) {
            dummy( dynamic_cast<C*>( a );
        }
        else
        {
            // ...
        }
    }

    void dummy( B * );
    void dummy( C * );
};

Due to my understanding this kind of declaration of function dummy() is not allowed

Technically the shown declarations of dummy are allowed. It may however be that you were hoping for the children to be concrete which is not the case.

Otherwise, just do no derivation of dummy (and remove it from BaseClass), so both classes DerivedClass1 and DerivedClass2 declare them independently.

This approach makes sense.

But then the idea of all classes derived from BaseClass have to implement dummy goes lost.

This is simply an idea that cannot be expressed with inheriting a base class. There is no concept of "child implements a member function with some argument". The argument type must be known, and all children must accept all arguments that the base accepts. And that is not fulfilled by your hierarchy, so the children remain abstract.

The structure that you are looking for instead seems to be concept . C++20 will introduce a programmatic way of specifying concepts; until then concepts are implicit: If you write a template, you can specify that a type argument must satisfy the requirement of having a member function dummy . Both of the "derived" types would satisfy that concept.

The point of virtual inheritance is that you can call your derived classes through a reference/pointer to the base type:

BaseClass* p = some_factory();

Hence when calling p.dummy(...) you don't (necessarily) know what runtime type is sitting behind the pointer. So you cannot sensibly decide whether you have to pass it an A , B , or C instance.

That's why you have to match the declaration type and figure out inside your overriding function what actual object you have. Depending on the context, perhaps a

  • static_cast : if you know for sure that the caller will pass you the correct runtime type
  • dynamic_cast : if you don't, but this incurs runtime costs and requries rtti
  • or your A type has virtual member functions that you can simply call through your A* argument

For instance:

void DerivedClass1::dummy(A* a) {
  if (B* const b = dynamic_cast<B*>(a)) {
    /* do stuff with b */
  } else {
    /* error handling -> wrong runtime type */
  }
}

C++ is not able to handle your situation because there is no way to put such a constraint on function calls by the base class. But changing the type of a virtual function can be done for instance for returned types, see covariance and contravariance :

class BaseClass {
public:
    virtual void dosomething(A*) = 0;
    virtual A* dummy(A*) = 0;
};

class DerivedClass1 : public BaseClass {
public:
    void dosomething(A* a) {B* b = dummy(a); do something;}
    //See return type was A* but now is B*
    B* dummy(A* a) { return dynamic_cast<B*>(a); }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM