简体   繁体   中英

C++ Polymorphism and Derived Class Types - “ugly programming” with pointer type casts

First up, I'm not sure exactly how to describe what I'm doing in one line... hence the slightly vague title.

The shortest description of the problem I can give is that "I have a function, and it should be able to take as an argument any of the many possible types of class, and these classes are all derived from a base class".

Specifically, I have 2 categories of class and both implement different types of method, which are similar but not exactly the same.

Perhaps it is better if I just give the example? You will see I do some slightly weird things with pointer type-casts. I don't think these are good programming practices. They are slightly strange at least, and I am wondering if there is an alternative, better method of doing things.

Okay so here goes my attempt at a simplified example:

class device
{
    // Nothing here - abstract base class
}

class inputDevice : device // inherit publicly, but it doesn't matter
{
    virtual input* getInput() { return m_input; } // input is a class
}

class outputDevice : device
{
    virtual output* getOutput() { return m_output; } // output is also a class
}

class inputoutputDevice : public inputDevice, public outputDevice
{
    // Inherits the get methods from input and output types
}

// elsewhere in program
void do_something(device* dev, int mode_flag)
{
    if(mode_flag == 1) // just an example
    {
        input* = ((inputDevice*)dev)->getInput(); // doing strange things with pointers
    }
    else if(mode_flag == 2)
    {
        output* = ((outputDevice*)dev)->getOutput(); // strange things with pointers
    }
    else if(mode_flag == 3)
    {

    }
}

So you see that the subtly here is that the function has some behavior dependent on whether we are dealing with an argument which is an input device or an output device.

I guess I could overload the function many times, but there could be many different types of input, output or both input and output devices... so that would be a rather convoluted method.

Putting the "get" methods into the base class doesn't seem like a good idea either, because the derived classes should NOT have the getInput() method if the device is an OUTPUT device. And similarly, and INPUT device should not have a getOutput() method. Conceptually that just doesn't seem right.

I hope that I explained that clearly enough and didn't make any mistakes.

To expand on my comment, if you look at eg this input/output library reference you will see a class diagram that in a way reminds much of your class hierarchy: There's a base-class (two actually), an "input" class, and "output" class, and an "input/output" class that inherits from both the "input" and "output" class.

However, you never really directly references the base classes std::basic_ios or std::ios_base , instead once only uses references to std::ostream for any output stream, and std::istream for any input streams (and std::iostream for any input and output stream).

For example, to overload the input operator >> your function takes a reference to a std::istream object:

std::istream& operator>>(std::istream& input_stream, some_type& dest);

Even for more generic functions you take a reference to either an std::istream , an std::ostream or an std::iostream object. You never use the base class std::basic_ios just because of the problems you have.


To relate more to your problem and how to solve it, use two function overloads, one for the input device, and one for the output device. It makes more sense because first of all you wont have the problem with checking type and casting, and also because the two functions will operate quite differently depending on if you are doing input or output anyway, and trying to mix it both into a single function just makes the code much more unmaintainable.

So you should instead have eg

void do_something(inputDevice& device);

and

void do_something(outputDevice& device);

There is an interesting design issue in your function do_something() : you assume that the type of the device corresponds to the mode parameter, but you have no way of verifying it.

Alternative 1: use dynamic cast

First of all, as you expect your device class to be polymorphic, you should foresee a virtual destructor. This would ensure that device is also polymorphic.

Then you can make use of dynamic casting to make your code reliable (here I assumed that mdode 3 was for input/output, but it's just for the general idea):

void do_something(device* dev, int mode_flag)
{
    if(mode_flag == 1 || mode_flag==3) // just an example
    {
        inputDevice* id=dynamic_cast<inputDevice*>(dev); // NULL if it's not an input device 
        if (id) {  
           input* in = id->getInput(); // doing strange things with pointers
        }
        else cout << "Invalid input mode for device"<<endl; 
    }
    if(mode_flag == 2 || mode_flag==3) 
    {
        outputDevice* od=dynamic_cast<outputDevice*>(dev);  
        if (od) {  
           output* out = od->getOutput(); 
        }
        else cout << "Invalid output mode for device"<<endl; 
    }
    // ...
}

Alternative 2: make do_something a method

I don't know how complex it is, but if you intend to do_something with any kind of devices, you could just make it a method.

class device {
public:
    virtual void do_something(int mode_flag) = 0; 
    virtual ~device() {}
};

You'll get the idea. Of course, you could also have a mix having a global do_something() function doing general steps and calling member functions for the part which should depend on the type of device.

Other remarks

Note that your inputoutputDevice inherits twice from device. As soon as you have some members in device, this might lead to ambiguity. I'd therefore suggest you consider virtual inheritance for the device class.

class inputDevice : public virtual device
...;

class outputDevice : public virtual device
...;

Another approach could be to have a more elaborate input/output interface in device:

class device {
public:  
    virtual bool can_input() = 0;    // what can the device do ? 
    virtual bool can_output() = 0; 
    virtual input* getInput() = 0; 
    virtual void  setOutput(output*) = 0; 
    virtual ~device() {};
};
class inputDevice : public virtual device {
    bool can_input() { return true; }
    bool can_output() { return false; }
    input* getInput() { return m_input; } // input is a class
    void  setOutput(output*) { throw 1; } // should never be called ! 
}; 
...
void do_something(device* dev, int mode_flag)
{
    if(mode_flag == 1 && dev->can_input() ) // just an example
        ...
    ...
}    

Since the problem domain is quite broad it is impossible to give an exact answer, but since it mentions device the linux kernel device model might be suitable.

See the linux-kernel Tag wiki for a deep dive. Look into LDD3 in there, since it's a free ebook, you can have a look how the kernel works internally.

The general concept of the linux kernel is that every device is represented by files. Hence you driver exports a file descriptors which have a vtable (See fs.h ).

One of the simplest character devices is the named pipe (See its vtable and there is also all the function definitions in the file).

A simple C++ conversion could look like:

struct abstract_dev {
   virtual int read(input *) { return -1; /* fail */ }
   virtual int write(output *) { return -1; /* fail */ }
   virtual int ioctl(int cmd, void **args) { return -1; }
};

struct input_dev : public abstract_dev {
  input_dev() : state(0) {}
  int state;
  int read(input *) override { 
    if (state != 2) {
      return -1;
    }
    /* do smth */ 
    return 0;
  }
  int ioctl(int cmd, void **args) override {
    if (cmd == 2) { state = 2;  return 0;}
    return -1;
  }
};

For modes the kernel uses the ioctl system call to set the mode (as a control plane) and saves the state in the device-driver. and subsequent reads and writes take the mode into account. In the named pipe example you can change the internal buffer size, by setting the FIONREAD value.

I hope this helps to find a solution to your problem.

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