当前位置: 首页 > 知识库问答 >
问题:

C多态性和派生类类型-指针类型转换的“丑陋编程”

白侯林
2023-03-14

首先,我不确定如何准确地用一行来描述我在做什么...因此标题有点模糊。

我能给出的问题的最短描述是“我有一个函数,它应该能够将许多可能的类类型中的任何一个作为参数,这些类都是从基类派生的”。

具体来说,我有两个类别的类,它们都实现了不同类型的方法,它们很相似,但不完全相同。

也许我只是举个例子会更好?你会看到我用指针类型转换做了一些稍微奇怪的事情。我不认为这些是好的编程实践。它们至少有点奇怪,我想知道是否有替代的、更好的做事方法。

好的,下面是我尝试的一个简化示例:

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)
    {

    }
}

所以你看到这里的微妙之处在于,该函数具有一些行为,这取决于我们是在处理一个参数,即输入设备还是输出设备。

我想我可以多次重载函数,但可能有许多不同类型的输入,输出或输入和输出设备...因此,这将是一个相当复杂的方法。

将“get”方法放入基类似乎也不是一个好主意,因为如果设备是输出设备,派生类就不应该有< code>getInput()方法。同样,输入设备也不应该有< code>getOutput()方法。从概念上讲,这似乎并不正确。

我希望我解释得足够清楚,没有犯任何错误。

共有3个答案

伯英武
2023-03-14

由于问题领域相当广泛,不可能给出精确的答案,但是由于它提到了设备,linux内核设备模型可能是合适的。

请参阅linux-core Tag wiki以深入了解。在那里查看LDD3,因为它是一本免费的电子书,您可以查看内核内部的工作原理。

linux内核的一般概念是每个设备都由文件表示。因此,您的驱动程序导出具有vtable的文件描述符(参见fs. h)。

最简单的字符设备之一是命名管道(见其vtable,文件中还有所有函数定义)。

一个简单的 C 转换可能如下所示:

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;
  }
};

对于模式,内核使用ioctl系统调用来设置模式(作为控制面)并将状态保存在设备驱动程序中。随后的读取和写入会考虑到模式。在命名管道示例中,您可以通过设置FIONREAD值来更改内部缓冲区大小。

我希望这有助于解决你的问题。

束俊材
2023-03-14

函数<code>do_something()

首先,当您期望您的设备类是多态的时,您应该预见到一个虚拟析构函数。这将确保设备也是多态的。

然后,您可以利用动态转换来使您的代码可靠(这里我假设mdode 3是用于输入/输出的,但这只是一般的想法):

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; 
    }
    // ...
}

我不知道它有多复杂,但如果你打算用任何类型的设备做一些事情,你可以把它变成一种方法

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

你会明白的。当然,你也可以有一个组合,它有一个全局do_something()函数来执行一般步骤,并为应该取决于设备类型的部分调用成员函数。

请注意,您的<code>inputoutputDevice<code>从设备继承了两次。一旦设备中有了一些成员,这可能会导致歧义。因此,我建议您考虑设备类的虚拟继承。

class inputDevice : public virtual device
...;

class outputDevice : public virtual 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
        ...
    ...
}    
范金鑫
2023-03-14

为了扩展我的评论,如果您查看例如这个输入/输出库参考,您将看到一个类图,它在某种程度上让您想起了您的类层次结构:有一个基类(实际上是两个)、一个“输入”类和“输出”类,以及一个继承自“输入”和“输出”类的“输入/输出”类。

但是,您从未真正直接引用基类 std::basic_iosstd::ios_base,而只对任何输出流使用对 std::ostream 的引用,对任何输入流使用 std::istream 的引用(对任何输入和输出流使用 std::iostream)。

例如,要重载输入运算符

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

即使对于更通用的函数,您也可以引用标准::istream标准::ostream标准::iostream对象。您永远不会仅仅因为遇到问题而使用基类 std::basic_ios

为了与您的问题以及如何解决它有更多的联系,请使用两个函数重载,一个用于输入设备,一个用于输出设备。这更有意义,因为首先您不会在检查类型和转换方面遇到问题,而且还因为这两个函数的操作会完全不同,具体取决于您是在进行输入还是输出,并且试图将其混合到一个函数中只会使代码更难维护。

所以你应该改为,例如。

void do_something(inputDevice& device);

void do_something(outputDevice& device);

 类似资料:
  • 对于下面的代码,当基类指针被分配给派生类时,我有关于多态性的问题。 当派生类的对象直接使用print函数时,输出是显而易见的。 当我使用基类指针并指向派生类的对象时,会使用基类的print函数,但输出的是派生对象的信息。有人能详细解释一下吗?谢谢!

  • 公有派生类的对象可作为其相应基类的对象处理,这使得一些有意义的操作成为可能。例如,从某个特定基类派生出来的各种类,尽管这些类的对象彼此之间互不相同,但是仍然能够建立这些对象的链表,只要把这些对象作为基类对象处理就可以了。然而反过来是不行的,基类的对象不能自动成为派生类的对象。 常见编程错误 9.1 将基类对象作为派生类对象处理。 程序员可以用显式类型转换把基类指针强制转换为派生类指针。但是,如果要

  • 指针变量数据类型的强制转换 必须显式强制类型转换,不允许隐式类型转换 指向空间的强制类型转换,本质上就是普通变量的强制类型转换 int a = 10; float b = 3.14; int *pa = &a; float *pb = &b; *pa = (int)*pb; // 等价于 a = (int)b; 指针本身强制类型转换,改变的是对其指向空间的引用方式(空间大小和存储结构) int

  • 本文向大家介绍C#中的自动类型转换和强制类型转换,包括了C#中的自动类型转换和强制类型转换的使用技巧和注意事项,需要的朋友参考一下 前面已经认识了不同的数据类型,你们有没有尝试过让不同的数据类型进行运算呢? 运行结果是:1 我们把一个整型的变量赋值给了一个浮点型的变量,可以正常的输出,如果我们把一个浮点型的变量赋值给一个整型的变量呢? 这样就会报错。 为什么呢?因为我们之前说过,变量就像一个容器,

  • 我要做的是将值存储在元素strucz数组中(该数组有一个成员void*data) 我已经把数组拼成了3个元素 我知道如果数据是int,你可以这样做,但是如果数据是空的,你怎么做呢* 我试过打字 但我得到消息预期表达式之前'in' 我的第二个问题是如何在元素结构中存储字符串(我不能使另一个成员成为字符串,它必须使用void指针) 我可以用strcopy或sscanf吗? 下面的主要样本,以防我做错了

  • 8. 函数类型和函数指针类型 在C语言中,函数也是一种类型,可以定义指向函数的指针。我们知道,指针变量的内存单元存放一个地址值,而函数指针存放的就是函数的入口地址(位于.text段)。下面看一个简单的例子: 例 23.3. 函数指针 #include <stdio.h> void say_hello(const char *str) { printf("Hello %s\n", str); }