【问题标题】:Code structure for different versions of devices不同版本设备的代码结构
【发布时间】:2019-10-06 12:36:51
【问题描述】:

我正在编写一个“设备驱动程序”(C++14),它可以处理用于不同版本设备的多个版本的协议。此设备驱动程序在外部 PC 上运行,该 PC 使用基于 HTTP 的协议通过以太网与设备通信。所有版本都有共同的功能,但某些功能可能会在某些版本的协议中增加。

下面是一个例子:

class ProtocolBase {
public:
    virtual void reset_parameters() {
        std::cout << "reset parameters" << std::endl;
    }

    virtual void set_parameters() {
        std::cout << "set parameters" << std::endl;
    }
};

class ProtocolV1 : public ProtocolBase
{
public:
    void set_parameters() override {
        std::cout << "set parameters for V1" << std::endl;
    }
};

class ProtocolV2 : public ProtocolBase 
{
public:
    void set_parameters() override {
        std::cout << "set parameters for V2" << std::endl;
    }

    void reset_parameters() {
        std::cout << "reset parameters for V2" << std::endl;
    }

    void do_V2() {
        std::cout << "doing V2" << std::endl;
    }
};

下面是main

int main(int argc, char const *argv[])
{
    int version = std::atoi(argv[1]);

    std::unique_ptr<ProtocolBase> protocol = std::make_unique<ProtocolV1>();
    switch (version)
    {
    case 1:
        /* do nothing at the moment */
        break;
    case 2:
        protocol.reset(new ProtocolV2);
        break;
    default:
        break;
    }

    protocol->reset_parameters();

    if(ProtocolV2* p = dynamic_cast<ProtocolV2*>(protocol.get())) { //not sure about this
        p->do_V2();
    }else {
        std::cout << "This functionality is unavailable for this device" << std::endl;
    }
    protocol->set_parameters();
    return 0;
}

我感觉使用dynamic_cast 不是最好的方法。期待一些反馈。

编辑:根据@Ptaq666 的回答,我将ProtocolBaseProtocolV2 修改为:

class ProtocolBase {
public:
    virtual void do_V(){
        std::cerr << "This functionality is unavailable for this device" << std::endl;
    }
};
class ProtocolV2 : public ProtocolBase 
{
public:
    void do_V() override {
        std::cout << "doing V2" << std::endl;
    }
};

有了这个,就不再需要dynamic_cast,尽管基类必须知道所有的功能。这似乎是目前最好的解决方案。

【问题讨论】:

  • 很好的例子在这里:cs.chromium.org/chromium/src/third_party/blink/public/platform/… 输入事件类型(在您的情况下是协议版本)在输入事件构造中指定。输入事件类型由IsMouseEventType 等方法确定,在您的情况下为IsProtocolV2
  • 还有什么是 Protocol 在主要的第二行 std::unique_ptr&lt;Protocol&gt; protocol = std::make_unique&lt;ProtocolV1&gt;(); 你的意思是 ProtocolBase 吗?
  • 我的错,是的,我的意思是ProtocolBase
  • 感谢您的澄清!如果函数不依赖于派生类中可能不同的任何数据成员,我看不出有任何问题!您不确定的是什么?你有什么顾虑?
  • @Fareanor 你是对的,但在这种特定情况下,这并不重要!

标签: c++ architecture version driver


【解决方案1】:

一般来说,这取决于派生类ProtocolV1ProtocolV2是如何形成的,以及各自的成员函数是否要使用不同的数据成员,数据成员和天气是什么!

原因很简单,因为不依赖于成员数据,成员函数只对调用它们的对象的类型敏感,而不是它们的值/状态

这就像有一个(非成员)函数重载,如:

void function(ProtocolV1 *){
        std::cout << "set parameters for V1" << std::endl;
}
void function(ProtocolV2 *){
        std::cout << "set parameters for V2" << std::endl;
}

然后用ProtocolV1 *类型的指针调用一次,然后用ProtocolV2 *类型的空指针调用一次。

如果您喜欢问题中提出的用法的替代方案,您甚至可以使用 C 样式转换: 结果是 SAME

最后,如果您要调用成员函数,然后从中调用另一个函数,该函数需要一些数据成员,这在派生类中是不同作为其参数的,那么你不能使用任何类型转换,除非你引入某种形式的补偿来填充类型转换中没有出现的数据!

祝你好运!

【讨论】:

  • @harsh 如果这有帮助,请考虑投票/接受它!
  • Fareanor 关于ProtocolV2* p 生命周期的评论并不是真正的话题,内存管理也没有问题。您使用 nullptr cast 和 nullptr 取消引用的示例也是错误的。取消引用空指针是未定义的行为 (ref)。最后,问题是关于如何正确设计系统(使用动态转换或其他方法),而不是如何转换指针。
  • @Ptaq666 有趣的是,我正要问类似的问题,因为我开始怀疑自己!谢谢!
  • 但我有点困惑,你是给我穿衣服还是@Fareanor 还是我,还是两者兼而有之?
  • @Ptaq666 现在我正在考虑它,我非常怀疑!我可以将其作为问题发布吗?
【解决方案2】:

在大多数情况下,选择合适的系统架构时,答案是 “这取决于” :)。最舒适的解决方案是引入特定于协议的行为 构造函数中的ProtocolBase 子类

class ProtocolV2 : public ProtocolBase 
{
public:
    ProtocolV2::ProtocolV2(args) {
        // set some params that will determine when do_V2() is called
        // it can be some numeric setting, a callback, or similar
    }
    void set_parameters() override {
        // you can use V2 behavior here maybe?
        std::cout << "set parameters for V2" << std::endl;
    }

    void reset_parameters() override {
        // or here maybe?
        std::cout << "reset parameters for V2" << std::endl;
    }

private:
    void do_V2() {
        std::cout << "doing V2" << std::endl;
    }
};

如果由于某种原因您无法执行此操作,则可以将 do_V2() 保持为公开状态 非虚拟方法,但在传递 ProtocolV2 作为指向 ProtocolBase 的指针之前调用它 到将使用它的系统。当然限制是do_V2只能在外面调用 您的系统范围,这可能无法真正解决问题。

另一种选择是将do_V2() 实际移动到界面:

class ProtocolBase {
public:
    virtual void reset_parameters() {
        std::cout << "reset parameters" << std::endl;
    }
    virtual void set_parameters() {
        std::cout << "set parameters" << std::endl;
    }
    virtual void do_V2() {
        std::cout << "not supported" << std::endl;
    }
};

并默认将其实现为“不支持”行为。只有ProtocolV2 会实现此行为 作为协议的有效部分。

最后,如果以上都不行,你当然可以按照你的建议使用dynamic_cast。 我个人尽量避免使用dynamic_cast,因为我的办公室同事肯定会开始滥用它, 但在某些情况下,这是一个正确的解决方案。

此外,如果您决定强制转换指针,请将 std::shared_ptrdynamic_pointer_cast 一起使用,而不是从 unique_ptr 访问原始指针。

【讨论】:

  • 我想我更喜欢使用您的第二个选项,将功能移动到界面。原因是,我可能有超过 2 种协议变体。我将不得不跟踪在哪个协议中实现了哪个功能。最简单的方法是在基类中拥有一个“空”虚函数,并由相应的派生类实现它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多