首先,我不确定如何在一行中描述我正在做的事情……因此标题略显模糊。
对于这个问题,我能给出的最简短的描述是"我有一个函数,它应该能够接受许多可能的类类型中的任何一个作为参数,并且这些类都是从基类派生的"。
具体来说,我有2类,它们都实现了不同类型的方法,它们相似但不完全相同。
也许我举个例子会更好?你会看到我用指针类型转换做了一些有点奇怪的事情。我不认为这些是好的编程实践。他们至少有点奇怪,我想知道是否有一种替代的,更好的方法来做事。
好的,下面是我尝试的一个简化的例子:
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"方法放入基类中似乎也不是一个好主意,因为如果设备是OUTPUT设备,派生类不应该具有getInput()
方法。同样,INPUT设备不应该有getOutput()
方法。从概念上讲,这似乎是不对的。
我希望我解释得足够清楚,没有犯任何错误。
扩展一下我的评论,如果你看一下这个输入/输出库参考,你会看到一个类图,它在某种程度上提醒了你的类层次结构:有一个基类(实际上是两个),一个"输入"类和"输出"类,还有一个"输入/输出"类继承自"输入"one_answers"输出"类。
然而,你从来没有真正直接引用基类std::basic_ios
或std::ios_base
,相反,只有一次使用引用std::ostream
的任何输出流,std::istream
的任何输入流(和std::iostream
的任何输入和输出流)。
>>
,函数需要一个对std::istream
对象的引用:
std::istream& operator>>(std::istream& input_stream, some_type& dest);
即使对于更通用的函数,您也可以引用std::istream
, std::ostream
或std::iostream
对象。你永远不要使用基类std::basic_ios
,仅仅因为你有问题。
为了更多地与你的问题和如何解决它联系起来,使用两个函数重载,一个用于输入设备,一个用于输出设备。它更有意义,因为首先你不会有检查类型和类型转换的问题,而且这两个函数的操作方式会完全不同,这取决于你是在做输入还是输出,而试图将它们混合到一个函数中只会使代码更加难以维护。
所以你应该用例如
void do_something(inputDevice& device);
和
void do_something(outputDevice& device);
在你的函数do_something()
中有一个有趣的设计问题:你假设设备的类型对应于模式参数,但是你没有办法验证它。
选项1:使用动态强制转换
首先,当你期望你的device
类是多态的,你应该预见到一个虚拟析构函数。这将确保设备也是多态的。
然后你可以使用动态强制转换使你的代码可靠(这里我假设mode 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;
}
// ...
}
选项2:make do_something a method我不知道它有多复杂,但是如果你打算用任何一种设备做某事,你可以把它变成一个方法。
class device {
public:
virtual void do_something(int mode_flag) = 0;
virtual ~device() {}
};
你会明白的。当然,您也可以使用一个全局do_something()
函数来执行一般步骤,并根据设备类型调用部分的成员函数。
注意你的inputoutputDevice
从device继承了两次。只要在device中有一些成员,这就可能导致歧义。因此,我建议您考虑对设备类进行虚拟继承。
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
...
...
}
由于问题领域相当广泛,因此不可能给出确切的答案,但由于它提到了设备,linux内核设备模型可能适合。
请参阅linux-kernel Tag wiki以获得更深入的了解。看看LDD3,因为它是一本免费的电子书,你可以看看内核是如何工作的。
linux内核的一般概念是每个设备都由文件表示。因此,你的驱动导出一个文件描述符,它有一个虚函数表(见fs.h)。
最简单的字符设备之一是命名管道(见它的虚函数表,文件中也有所有的函数定义)。
一个简单的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
值来更改内部缓冲区的大小。
我希望这能帮助你找到解决问题的办法。