如何混合多态性和模板

问题描述 投票:0回答:1

我有一个基类,为了便于论证,我们将其称为

Node
,它将具有任意数量的叶实现。现在我们将其限制为两个:
NodeA
NodeB
。它们需要不同的参数来构建。

我想创建一个节点,并且我有一个用于此目的的工厂类。工厂有模板功能

template<typename NodeType, typename...Args>
Node * createNode(Args&&... args) 
{
    auto const node = new NodeType(std::forward(args)...);
    /* other stuff*/
    return node;
}

这允许我创建新的节点类型,而不必担心向工厂添加额外的创建函数

auto const nodeA = factory->createNode<NodeA>("foo", 10);
auto const nodeB = factory->createNode<NodeB>("bar", "wibble", 100.0f);

但这里有一个转折点:返回的 actual 类型不仅取决于 NodeType 参数,还取决于工厂类型。

class NodeA1 : public NodeA {/*stuff*/};

class Factory1 : public Factory
{
    /* C++ won't allow me to virtualise (or overload?) createNode*/
    template<typename NodeType, typename...Args>
    Node * createNode(Args&&...args)
    {
        /* If NodeType is A, I want to return new NodeA1() */
        ???
    }
};

我不想有这样的界面

class Factory
{
    virtual Node * createNodeA(/*A args*/) = 0;
    virtual Node * createNodeB(/*B args*/) = 0;
};

因为扩展性很差(每个工厂类型都会受到参数更改的影响,并且必须扩展以创建新类型)。我担心我可能不可避免地必须这样做,但想知道是否有更干净的方法来实现这种设计?或者确实是更好的设计!

c++ oop
1个回答
0
投票

典型的方法是使用类型擦除,使用的类型擦除量取决于需要扩展的内容和具体内容。

由于

createNode
是模板化的,它不能是虚拟的,它能做的最好的事情就是将其参数转换为类型擦除的对象,然后将其传递给虚拟函数,我们称之为
createNode_impl

每个工厂将使用这个类型擦除的对象来解析所需的类型(NodeA、NodeB 等),然后构造自己的版本(NodeA1 等)

#include <iostream>
#include <string_view>
#include <memory>

struct Node {
    Node() = default;
    Node(Node&&) = default;
    Node(const Node&) = default;
    Node& operator=(Node&&) = default;
    Node& operator=(const Node&) = default;
    virtual ~Node() = default;

    virtual void foo() = 0;
};

struct ConstructionArgs
{
    virtual std::string_view name() = 0;
};

struct NodeAConstructionArgs : public ConstructionArgs
{
    std::string_view name() override
    {
        return "NodeA";
    }
    std::string data = "some_data";
};

struct NodeA : public Node
{
    NodeA(std::string s) : data{ std::move(s) } {}
    std::string data;
};

struct AbstractFactory
{
    template <typename T, typename...Args>
    std::unique_ptr<Node> createNode(Args&&...args)
    {
        if constexpr (std::same_as<T, NodeA>)
        {
            auto arg = NodeAConstructionArgs{ args... };
            return Construct_impl(arg);
        }
        // others
        return nullptr;
    }
    virtual std::unique_ptr<Node> createNode_impl(ConstructionArgs& args) = 0;
    virtual ~AbstractFactory() = default;
};

struct NodeA1 : public NodeA
{
    using NodeA::NodeA;
    virtual void foo() { std::cout << "A1"; }
};

struct Factory1 : public AbstractFactory
{
    std::unique_ptr<Node> createNode_impl(ConstructionArgs& args) override
    {
        if (auto* obj = dynamic_cast<NodeAConstructionArgs*>(&args))
        {
            return std::make_unique<NodeA1>(NodeA1{ std::move(obj->data) });
        }
        return nullptr;
    }
};

int main()
{
    Factory1 f;
    auto obj = f.Construct<NodeA>();
    obj->foo();
}

goldbolt 演示

© www.soinside.com 2019 - 2024. All rights reserved.