【问题标题】:How to use Curiously Recurring Template Pattern for Bridge Pattern?如何将奇怪重复的模板模式用于桥接模式?
【发布时间】:2023-10-01 08:47:01
【问题描述】:

我一直在研究 Curiously Recurring Template Pattern 以确定如何使用它来实现桥接设计模式。

我的问题是将 IBridgeConnector::GetBridgeImpl 方法连接(连接)到 Bridge::GetBridgeImpl 方法,因为覆盖方法实际上是模板化的。

由于虚拟调度在这种情况下不起作用,将这些方法相互指向的最佳方法是什么?职能代表?有更好的模式吗?

这应该怎么做?

感谢您的帮助!

在没有 shared_ptrs 以及 OpenGL 和 DirectX 调用的情况下,我可以尽可能地简化代码。 :) 希望这对将来的人有用!

#include <string>
/********************************************************************/
class BridgePart
{
public:
    BridgePart * OtherPart;
};

/********************************************************************/
// Connects a BridgeSource and a BridgeImplementation
class BridgeConnector
{
public:
    static BridgeConnector * implementor;

    // Need a way, (Function Delegates?) to point this method
    // This method will loop until stack overflow.
    template <typename ValueTemplateType>
    BridgePart * GetBridgeImpl(ValueTemplateType * source)
    {
        return implementor->GetBridgeImpl<ValueTemplateType>(source);
    }
};
BridgeConnector * BridgeConnector::implementor = nullptr;

/********************************************************************/
// Where the Magic is At, (CRTP)
template <typename BridgeImplementationTemplateType>
class Bridge : public BridgeConnector
{
public:
    template <typename ValueTemplateType>
    IBridgePart * GetBridgeImpl(IBridgePart * source)
    {
        // NOTE:  This method never gets called.
        // CRTP Magic Here to Semi-Specify Pure Virtual Methods
        return static_cast<BridgeImplementationTemplateType>(this)
            ->GetBridgeImpl( (ValueTemplateType) source);
    }
};

/********************************************************************/
class BridgeImplementation1 : 
    public Bridge<BridgeImplementation1>,
    public BridgePart
{
public:
    class CustomImpl : public BridgePart
    {
    public:
        template <typename SourceTemplateType>
        BridgePart(SourceTemplateType source){}
            /* Does proprietary stuff. */
    };

    template <typename ValueTemplateType>
    BridgePart * GetBridgeImpl(ValueTemplateType & source)
    {
        return new CustomImpl<ValueTemplateType>(source);       
    }
    // Constructor
    BridgeImplementation1()
    {
    }
};

/********************************************************************/
class BridgeSource1 : public BridgePart {};
class BridgeSource2 : public BridgePart {};
class Client
{
    BridgeSource1 source1;
    BridgeSource2 source2;
    BridgeConnector * connector;
    bool usingImpl1;

    Client()
    {
        usingImpl1 = true; // from config file.
        connector = new BridgeConnector();
        connector->implementor = usingImpl1 
            ? (BridgeConnector *) new BridgeImplementation1()
            : nullptr; // (BridgeConnector *) new BridgeImplementation2();  
        // removed to shorten code.
    }

    void Init()
    {
        source1.OtherPart = connector->GetBridgeImpl<BridgeSource1>(& source1);
        source2.OtherPart = connector->GetBridgeImpl<BridgeSource2>(& source2); 
    }
};

【问题讨论】:

  • 使用调试器,逐行逐行执行代码(单步执行调用的函数),您很快就会明白为什么会出现堆栈溢出。作为提示,您使用指针调用LoadDriver,并继续使用指针调用LoadDriver

标签: c++ c++11 design-patterns crtp bridge


【解决方案1】:

我觉得你对 CRTP 有点误解。它不是虚拟调度的插件替代品。在您的情况下,IBridge 没有虚拟调度,因此调用 IBridge->LoadDriver 将始终为您提供默认的基类实现,无论底层派生类如何。如果你想要一个通用接口,你需要某种虚拟调度。 CRTP 只是在不需要的情况下避免虚拟调度的一种方式,例如当基类成员函数调用其他虚拟函数时。

如果你想在你的情况下完全避免虚拟调度,你不能完全将客户端与桥解耦,你需要在桥上模板客户端,这并没有给你任何优势,只是在派生上模板。

这是一个做你想做的事的机会。它假设您通过 IBridge 传递一些通用指针,Bridge 使用 CRTP 来解释并传递给 BridgeImpl。显然,此时您已经放弃了对参数类型安全的所有希望。

class IBridge {
    virtual void LoadDriver(void *) = 0;
};

template <typename Impl>
class Bridge : public IBridge {
    void LoadDriver(void * v) override {
        static_cast<Impl*>(this)->LoadDriverImpl(*static_cast<Impl::arg_type *>(v));
    }
};

class BridgeImpl : public Bridge<BridgeImpl> {
    typedef std::string arg_type;
    void LoadDriverImpl(const std::string & s){ /*...*/ }
};

【讨论】:

  • 好的,我想我现在明白了虚拟调度与这个问题的相关性,IBridge::LoadDriver 没有被 Bridge::LoadDriver “覆盖”......我一直在尝试的解决方案避免使用某种静态函数指针来链接两者。我/真的/试图避免使用 RTTI 和动态强制转换,这就是我首先使用 CRTP 的原因。你会推荐什么?感谢您的帮助。
  • 虚拟调度不需要 RTTI/动态转换。从您给出的示例中,vanilla virtual dispatch 即 Derived 直接继承自 IBridge,它具有虚拟 LoadDriver,可能是最好的解决方案。如果成员函数调用更复杂,您可能会考虑在较低级别使用 CRTP,即 IBridge 有一个虚拟 LoadDriver,Bridge 使用 CRTP 覆盖并调用派生类上的非虚拟 LoadDriverImpl(这没有任何意义)拥有,但可能会在更复杂的用例中使用)
  • 整个上下文是Bridge API在Framework层,(library),被Application层消费,(lib),被Client层消费,(.exe)...客户端层提供 BridgeImplementation : public Bridge, public IBridgePart .. 在运行时,应用程序层调用基于框架层的服务定位器来查找在客户端层中实现的桥的另一端。除了 BridgeServiceLocator 将返回 IBridgePart::ICommandPattern 之外,应用程序层和框架层对实现一无所知。 HTH
  • 好的,我认为我所说的一切仍然适用。假设您当前的设计,使 IBridge::LoadDriver 虚拟并修复 Joachim 提到的问题 - 目前无法知道桥中的 ValueTemplateType 使用什么类型。您可能需要对 IBridge LoadDriver 进行模板化,通过虚拟调度调用 Bridge::LoadDriverImpl 函数,该函数通过 CRTP 调用实际实现的 LoadDriverImpl 函数
  • 此外,您的 IBridge 必须知道它在编译时可以采用的全部类型,除非您在运行时给它一些其他方法来确定它,目前您无法从 void 中获取* 当您在 IBridge::LoadDriver 中时返回 std::string。你真的需要一个完全任意的类型吗??
【解决方案2】:

Client::Init函数中调用LoadDriver时,ValueTemplateType模板参数为std::string*,即一个指针。桥接实现类BridgeImplementation1BridgeImplementation2 具有采用指针的函数。所以当编译器试图找到一个匹配的LoadDriver函数时,它不会考虑接受非指针参数的函数,只考虑接受指针参数的函数。

您应该更改BridgeImplementation1BridgeImplementation2 中的LoadDriver 函数以获取指针参数。

【讨论】:

  • 我不太确定我是否理解。接口和基桥类必须具有相同的签名才能覆盖,对吗?然后 Bridge 做了一些神奇的 static_cast 东西,所以不需要相同的签名。我不认为 Bridge::LoadDriver 方法会覆盖 IBridge::LoadDriver ...对不起,我认为我对模式的理解已经足够好了,只是对 C++ 语法不太了解。
  • @WindAndFlame IBridge 中的 LoadDriver 函数采用指针,您不能使用指针参数调用采用非指针参数的函数。将子类中的LoadDriver 函数更改为将指针 改为std::string
  • Joachim 是对的,如果原型不正确,在 C 中也会出现同样的问题
  • 我更改了已实现方法的名称以减少歧义...现在,我正在尝试解决 IBridge::LoadDriver() 和 Bridge::LoadDriver(我>)。 Virtual Dispatch 会因为模板而无法工作吗?你如何解决这个问题?
  • @WindAndFlame 将 void ImplementedLoadDriver(std::string v) 参数更改为 std::string *v