【问题标题】:How to achieve polymorphism with templated functions?如何使用模板函数实现多态性?
【发布时间】:2020-07-04 09:53:28
【问题描述】:

在我的项目中,我有一个带有接口的基本抽象类,派生类实现了该接口。这些派生类具有接受不同类型参数的通用函数。我使用函数模板在派生类中编写了这些通用函数。

我想将这些模板化函数添加到我的基类中的接口中。所以我可以实现多态性:在其他函数中接受基类,在派生类中调用这些模板函数。

当我们有普通函数时,我们会做 virtual 和 override,但你不能用模板函数做 virtual。

我尝试在我的抽象基类中执行纯抽象模板化函数,但它不起作用。

这是一个小程序,它具有我正在尝试实现的功能,但由于virtual <template... 而无法编译:

#include <vector>

class ObjectTransformerBaseAbstractClass {
public:
    virtual template<typename TStructure> TStructure ToStructure(std::vector<unsigned char> bytes) = 0;
    virtual template<typename TStructure> std::vector<unsigned char> ToBytes(TStructure structure) = 0;
};

class ObjectTransformer1 : public ObjectTransformerBaseAbstractClass {
    template <typename TStructure> TStructure ToStructure(std::vector<unsigned char> bytes) {
        // some implementation
    }

    template <typename TStructure> std::vector<unsigned char> ToBytes(TStructure structure) {
        // some implementation
    }
};

class ObjectTransformer2 : public ObjectTransformerBaseAbstractClass {
    template <typename TStructure> TStructure ToStructure(std::vector<unsigned char> bytes) {
        // some other implementation
    }
    template <typename TStructure>
    std::vector<unsigned char> ToBytes(TStructure structure) {
        // some other implementation
    }
};

template <typename TStructure>
void coutStructureBytes(ObjectTransformerBaseAbstractClass *objectTransformerBaseAbstractClass, TStructure structure) {
    // transform structure to bytes using the passed objectTransformerBaseAbstractClass object and cout it.
}

在我的基类中,我需要说“实现这些纯抽象泛型函数,它们接受不同类型的不同参数并在派生类中执行操作”。在我的派生类中,我需要实现这些接受不同类型参数的纯抽象泛型函数。

我不明白如何实现我想要的这个功能(你可以在上面的程序中看到,如果它编译和工作的话)。请推荐一个解决方案或解释如何使这项工作。

【问题讨论】:

  • "接受不同类型的参数" vs "不使用模板".???
  • 提示:请将您的代码简化为仅显示问题所在的示例。您的示例方法中的所有内容实际上只是在浪费读者时间。一个简单的打印就足够了!请删除所有 memcpy/resize,bla 的东西,因为它与具有未知参数类型的抽象基类的问题无关......
  • @Klaus 好的,我将示例简化为仅基类和派生类,并且不包括实现。

标签: c++ abstract-class function-templates


【解决方案1】:

您正在尝试使用声明为virtual 的方法模板。这是不可能的。另请参阅this answer 的相关问题。

您似乎正在尝试创建某种序列化/反序列化机制。您希望将不同的结构(在不同的类中实现)转换为字节,并希望能够通过以某种方式解释字节来重新创建正确类型的对象。

让我们首先处理序列化问题。您可以通过使用每个结构都应实现的序列化方法为所有结构定义一个接口来轻松解决这个问题:

class Structure
{
public:
    virtual std::vector<uint8_t> ToBytes() = 0;

    // any other functionality common to all structures
};

一个特定的结构将如下所示:

class SomeStructure: public Structure
{
public:
    SomeStructure(std::vector<uint8_t> bytes)
    {
        // deserialize (create object from bytes)
    }

    std::vector<uint8_t> ToBytes() override
    {
        // serialize
    }

    // explained below
    static constexpr uint8_t TypeId()
    {
        return 42;
    }

    // any specific methods for the structure
};

现在是反序列化问题。您需要有某种方法来识别字节流中的类类型。假设您的结构始终将唯一的类 ID 作为第一个字节写入。然后,您可以使用通过识别类型来创建正确对象的工厂:

class StructureBuilder
{
public:
    virtual Structure* FromBytes(std::vector<uint8_t> bytes) = 0;
    virtual uint8_t TypeId() = 0;
};

class StructureFactory
{
public:
    static StructureFactory& Instance()
    {
        // usual singleton stuff: create if not existing yet, then return instance
    }

    Structure* FromBytes(std::vector<uint8_t> bytes)
    {
        for (auto& builder : builders)
        {
            if (builder->TypeId() == bytes[0])
                return builder->BuildStructureFromBytes(bytes);
                
            return nullptr;
        }
    }

    void Add(StructureBuilder* builder)
    {
        // add to list
    }

    void Remove(StructureBuilder* builder)
    {
        // remove from list
    }

private:
    StructureFactory()
    {
    }
    std::list<StructureBuilder*> builders;
    static StructureFactory* instance;
};

构建器看起来都非常相似,因此您可以在此处使用一个模板,以便更轻松地为每个结构创建一个:

template <typename T>
class TypedStructureBuilder: public StructureBuilder
{
public:
    TypedStructureBuilder()
    {
        StructureFactory::Instance().Add(this);
    }

    virtual ~TypedStructureBuilder()
    {
        StructureFactory::Instance().Remove(this);
    }

    Structure* BuildStructureFromBytes(std::vector<uint8_t> bytes) override
    {
        return new T(bytes);
    }

    uint8_t TypeId() override
    {
        return T::TypeId();
    }
};

那么你需要做的就是在工厂注册一些新结构,把它放在某个地方(我会把它放在SomeStructure.cpp):

static TypedStructureBuilder<SomeStructure> builder;

希望这会有所帮助。

【讨论】:

  • 我理解不能同时使用模板和虚函数。这就是我试图解决我的问题但失败了。我确定我的问题有解决方案并且可以解决我的问题,只是在这种情况下我不能同时使用模板和虚函数。
  • 我仔细研究了您想要实现的目标,并编辑了我的答案以提出可能的解决方案。
  • 感谢您不遗余力地编写此示例。不过,我的序列化示例只是一个示例。我在使用模板化方法实现多态和继承以减少代码重复之后,序列化示例只是一个示例。请参阅我的回答我如何使用通用模板函数实现多态性和继承:stackoverflow.com/a/62802439/6693304
【解决方案2】:

这是我试图实现的行为的 C# 版本:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace baseAbstractTemplates.NET {
    interface IObjectTransformer {
        TStructure ToStructure&lt;TStructure&gt;(ICollection&lt;byte&gt; bytes);
        ICollection&lt;byte&gt; ToBytes&lt;TStructure&gt;(TStructure structure);
    };

    class ObjectTransformer1 : IObjectTransformer {

        #region Implementation of IObjectTransformerBaseAbstractClass

        public TStructure ToStructure&lt;TStructure&gt;(ICollection&lt;byte&gt; bytes) {
            throw new NotImplementedException();
        }

        public ICollection&lt;byte&gt; ToBytes&lt;TStructure&gt;(TStructure structure) {
            throw new NotImplementedException();
        }

        #endregion

    }

    class ObjectTransformer2 : IObjectTransformer {

        #region Implementation of IObjectTransformerBaseAbstractClass

        public TStructure ToStructure&lt;TStructure&gt;(ICollection&lt;byte&gt; bytes) {
            throw new NotImplementedException();
        }

        public ICollection&lt;byte&gt; ToBytes&lt;TStructure&gt;(TStructure structure) {
            throw new NotImplementedException();
        }

        #endregion

    }

    class Program {
        public static void CoutStructureBytes(IObjectTransformer objectTransformer) {
            var bytes = objectTransformer.ToBytes(3);
            Console.WriteLine(bytes);
        }

        static void Main(string[] args) {
            ObjectTransformer1 objectTransformer1 = new ObjectTransformer1();
            ObjectTransformer2 objectTransformer2 = new ObjectTransformer2();
            CoutStructureBytes(objectTransformer1);
            CoutStructureBytes(objectTransformer2);
        }
    }
}

在 C# 中,它只适用于“哈哈 C# 接口、模板、多态性 go brrr”风格。即使您完全不熟悉 C# 但有 C++ 知识,我相信您也可以很好地遵循 C# 代码。

这编译和运行得很好,抛出 NotImplementedException 因为没有实现。

但是在 C++ 中,与 C# 不同,我不能只使用常用工具来使用模板、继承和多态性接口:纯抽象函数(我在派生类中重写)、模板、继承和方法重写。因为我不能将方法模板与虚拟混合。

经过几天的研究,我终于找到了它是如何在这里完成的: C++ is Lazy: CRTP - ModernesCpp.com[^]

CRTP 和静态多态性。最后,我试图实现的行为的 C++ 版本:

// bastAbstractTemplates.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;string&gt;

template&lt;typename DerivedClass&gt;
class IObjectTransformer {
public:
    template&lt;typename TStructure&gt; TStructure ToStructure(std::vector&lt;unsigned char&gt; bytes);
    template&lt;typename TStructure&gt; std::vector&lt;unsigned char&gt; ToBytes(TStructure structure);
private:
    IObjectTransformer() = default;

    friend DerivedClass;
};

template &lt;typename DerivedClass&gt;
template &lt;typename TStructure&gt;
TStructure IObjectTransformer&lt;DerivedClass&gt;::ToStructure(std::vector&lt;unsigned char&gt; bytes) {
    return static_cast&lt;DerivedClass*&gt;(this)-&gt;ToStructure(bytes);
}

template &lt;typename DerivedClass&gt;
template &lt;typename TStructure&gt;
std::vector&lt;unsigned char&gt; IObjectTransformer&lt;DerivedClass&gt;::ToBytes(TStructure structure) {
    return static_cast&lt;DerivedClass*&gt;(this)-&gt;ToBytes(structure);
}

class ObjectTransformer1 : public IObjectTransformer&lt;ObjectTransformer1&gt; {
public:
    template &lt;typename TStructure&gt; TStructure ToStructure(std::vector&lt;unsigned char&gt; bytes) {
        unsigned char* bytePointer = &amp;bytes[0];
        TStructure structure = reinterpret_cast&lt;TStructure&gt;(*bytePointer);
        return structure;
    }

    template &lt;typename TStructure&gt; std::vector&lt;unsigned char&gt; ToBytes(TStructure structure) {
        char* bytesArray = reinterpret_cast&lt;char*&gt;(&amp;structure);
        auto byteVec = std::vector&lt;unsigned char&gt;(bytesArray, bytesArray + sizeof(TStructure));
        return byteVec;
    }
};

class ObjectTransformer2 : public IObjectTransformer&lt;ObjectTransformer2&gt; {
public:
    template &lt;typename TStructure&gt; TStructure ToStructure(std::vector&lt;unsigned char&gt; bytes) {
        TStructure structure{};
        std::memcpy(&amp;structure, &amp;bytes[0], sizeof(TStructure));
        return structure;
    }
    template &lt;typename TStructure&gt;
    std::vector&lt;unsigned char&gt; ToBytes(TStructure structure) {
        std::vector&lt;unsigned char&gt; bytes{};
        bytes.resize(sizeof(TStructure));
        std::memcpy(&amp;bytes[0], &amp;structure, sizeof(TStructure));
        return bytes;
    }
};


template &lt;typename DerivedClass, typename TStructure&gt;
void CoutStructureBytes(IObjectTransformer&lt;DerivedClass&gt; *objectTransformerBaseAbstractClass, TStructure structure) {
    auto bytes = objectTransformerBaseAbstractClass-&gt;template ToBytes&lt;TStructure&gt;(structure);
    for(auto byte : bytes) {
        std::cout &lt;&lt; std::to_string(byte) &lt;&lt; ' ';
    }
    std::cout &lt;&lt; std::endl;
}

int main() {
    ObjectTransformer1 objectTransformer1{};
    ObjectTransformer1 objectTransformer2{};

    int integer = 5;
    float someFloat = 9.79f;

    CoutStructureBytes(&amp;objectTransformer1, integer);
    CoutStructureBytes(&amp;objectTransformer2, someFloat);
}

【讨论】:

  • 虽然我自己写了这段代码,但我自己也勉强能看懂,但我想我会习惯的。
猜你喜欢
  • 1970-01-01
  • 2020-05-30
  • 1970-01-01
  • 1970-01-01
  • 2020-10-04
  • 2015-12-10
  • 1970-01-01
  • 2022-06-12
  • 1970-01-01
相关资源
最近更新 更多