【问题标题】:Select object for call function at run time, without base class and templates在运行时为调用函数选择对象,没有基类和模板
【发布时间】:2015-10-28 13:35:19
【问题描述】:

我有两个具有相同接口方法的类:

struct ImplGenerated {
    int foo(int x, int y);
    void bar(double x);
    ....
};

struct ImplCustom {
    int foo(int x, int y);
    void bar(double x);
    .....
};

还有类包装器:

struct Wrapper {
    Wrapper(ImplGenerated * i): m_generated(i), m_custom(0) {}
    Wrapper(ImplCustom * i): m_generated(0), m_custom(i) {}
    int foo(int x, int y);
    void bar(double x);
    ....
private:
    ??? getImpl();
    ImplGenerated * m_generated;
    ImplCustom * m_custom;
};

int Wrapper::foo(int x, int y) {
    return getImpl()->foo(x, y);
}
void Wrapper::bar(double x) {
    getImpl()->bar(x);
}

是否可以编写一些 C++ 构造(类或任何其他,但不是宏)来代替 getImpl() 来解析当前的实现对象并调用相应的方法? 像这样:

???? getImpl() {
    return m_custom ? m_custom : m_generated;
}

注意: 只能应用对 ImplCustom 的更改(添加基类或制作模板或其他),ImplGenerated 是由外部项目自动生成的,因此无法更改(添加基类是不可能的)。 Wrapper 不能是模板,因为是接口类。

更新: 从 ImplGenerated 派生 ImplCustom 是不可能的。

【问题讨论】:

  • 能否从ImplGenerated派生出ImplCustom
  • 您不能在运行时更改函数的返回类型。选项是运行时多态 - 基类指针/引用和虚函数,或编译时多态 - 生成多个函数的模板。

标签: c++


【解决方案1】:

我在这里看到的解决方案是创建一个包装器

此解决方案的目标是为您的两个不相关的类生成一个接口。

让我们从创建一个基类开始:

struct ImplInterface {
    virtual int foo(int x, int y) = 0;
    virtual void bar(double x) = 0;
    // ...
};

现在您可以为您获得的每个 Impl 创建包装器:

struct GeneratedWrapper : ImplInterface {
    virtual int foo(int x, int y) {
        _impl.foo(x, y);
    }

    virtual void bar(double x) {
        _impl.bar(x);
    }

private:
    ImplGenerated _impl;
};

struct CustomWrapper : ImplInterface {
    virtual int foo(int x, int y) {
        _impl.foo(x, y);
    }

    virtual void bar(double x) {
        _impl.bar(x);
    }

private:
    ImplCustom _impl;
};

现在你可以像这样使用这些包装器:

ImplInterface* wrapper = new GeneratedWrapper(implGenerated);

使用模板可以使这个方法更短,让我们为您的新界面制作一个包装器:

template<typename T>
struct EveryImplWrapper : ImplInterface {
    virtual int foo(int x, int y) {
        _impl.foo(x, y);
    }

    virtual void bar(double x) {
        _impl.bar(x);
    }

private:
    T _impl;
};

现在你可以像这样使用它了:

ImplInterface* = new EveryImplWrapper<ImplCustom>(implCustom);

【讨论】:

  • 有趣的想法!谢谢!你让我思考如何将它应用到我的项目中。
【解决方案2】:

如果您不能修改现有的类,您仍然可以添加一个外观来提供事后多态性。那就是:

包装器不能是模板,因为是接口类

只是部分正确。你可以有一个非模板化的接口 (ABC) 和一个模板化的具体子类。

// publically visible parts
struct ImplInterface {
    virtual ~ImplInterface() {}
    virtual int foo(int x, int y) = 0;
    virtual void bar(double x) = 0;
    ....
};

struct Wrapper {
    // ...
    ImplInterface *Wrapper::getImpl();
    // ... do you want to keep the same impl selection across calls?
    ImplInterface *m_impl;
};

// implementation details can be hidden in a cpp file

template <typename RealImpl>
struct ImplFacade: ImplInterface {
    RealImpl pimpl_;

    explicit ImplFacade(RealImpl *impl) : pimpl_(impl) {}

    int foo(int x, int y) override { return pimpl_->foo(x,y); }
    void bar(double x)    override { pimpl_->bar(x); }
};

ImplInterface *Wrapper::getImpl() {
    if (!m_impl) {
        if (m_custom)
            m_impl = new ImplFacade<ImplCustom>(m_custom);
        else
            m_impl = new ImplFacade<ImplGenerated>(m_generated);
    }
    return m_impl;
}

理想情况下,您应该在实际代码中为新成员使用unique_ptr

【讨论】:

  • 是理想的解决方案,但在目前的项目中很难实现,有数百种方法和接口
  • 如果你有数百个方法和接口,nothing 会很容易,除非你能找到生成代码的方法。
【解决方案3】:

如果您可以使用 c++11 标准,您可以使用std::function 来完成此操作:

struct Wrapper {
    Wrapper(ImplGenerated * i):
        foo(std::bind(&ImplGenerated::foo, i)),
        bar(std::bind(&ImplGenerated::bar, i)) {}

    Wrapper(ImplCustom * i):
        foo(std::bind(&ImplCustom ::foo, i)),
        bar(std::bind(&ImplCustom ::bar, i)) {}

    //Now the functions are member variables but it works the same way
    std::function<int(int x, int y)> foo;
    std::function<void(double x)> bar;
    ....

//If you don't Need to destruct the impl-objects then you don't even Need to store them
};

【讨论】:

  • 这意味着很多动态分配和很多间接性:(
  • @GuillaumeRacicot 实际上,std::function 有很好的表现。我也认为不会有太多动态分配,因为它只是绑定一个成员函数。
  • @EvgenyKlyuzin 不客气!其实如果你仔细看你会发现你不需要写那么多代码,因为你不需要写函数的实现......你只需将它们绑定在构造函数中
  • @Thomas Sparber 也许吧!看看std::function vs virtual dispatch 的性能会很有趣。
  • @GuillaumeRacicot 你可以看到 :-) 见这里:stackoverflow.com/questions/14306497/…
【解决方案4】:

我相信你想要的是使用继承/接口:

struct ImplInterface {
    virtual int foo(int x, int y) = 0;
    virtual void bar(double x) = 0;
    ....
};

struct ImplGenerated : public ImplInterface {
    virtual int foo(int x, int y);
    virtual void bar(double x);
    ....
};

struct ImplCustom : public ImplInterface {
    virtual int foo(int x, int y);
    virtual void bar(double x);
    .....
};

现在您的getImpl() 将返回ImplInterface*

或者,如果您无法添加基类,则在您的 Wrapper 类中而不是 getImpl() 中执行以下操作:

int foo(int x, int y)
{
    if (m_generated != nullptr)
    {
         return m_generated->foo(x,y);
    }
    else if (m_custom != nullptr)
    {
         return m_custom->foo(x, y);
    }
    throw "hey dude!";
}

我知道这需要做很多工作,但是您没有可使用的基类。

【讨论】:

  • 感谢您的回答!但我无法为 ImplGenerated 添加基类
  • @EvgenyKlyuzin,请参阅我的编辑。另一种选择,如果ImplGenerated 类的方法是virtual,您可以从ImplGenerated 派生ImplCustom 并覆盖这些方法。
  • 这个解决方案目前应用在我的项目中。每个接口方法都有很多臃肿的代码。那是实际问题。我有数百种具有相同逻辑的方法。在开发过程中难以维护、编写测试和牢记。
  • @EvgenyKlyuzin,如果无法从ImplGenerated 继承,也许 Guillaume Racicot 的答案是可以使用的。
  • 我不会 -1 你的答案,因为它可以工作,但它不能解决实际问题。
猜你喜欢
  • 1970-01-01
  • 2019-12-12
  • 2020-05-19
  • 2012-03-06
  • 2013-03-15
  • 1970-01-01
  • 1970-01-01
  • 2022-01-11
  • 1970-01-01
相关资源
最近更新 更多