KulaGGin Ответов: 3

Как достичь полиморфизма с помощью шаблонных функций?


В моем проекте у меня есть базовый абстрактный класс с интерфейсом, который реализуют производные классы. Эти производные классы имеют универсальные функции, которые принимают параметры различных типов. Я написал эти универсальные функции в своих производных классах, используя шаблоны функций.

Я хочу добавить эти шаблонные функции в интерфейс моего базового класса. Таким образом, я могу достичь полиморфизма: принять базовый класс в других функциях и вызвать эти шаблонные функции в производных классах.

Когда у нас есть нормальные функции, мы делаем виртуальные и переопределяем, но вы не можете делать виртуальные с шаблонными функциями.

Я пытался сделать чисто абстрактные шаблонные функции в моем абстрактном базовом классе, но это не работает.

Вот небольшая программа с функциональностью, которую я пытаюсь достичь, которая не компилируется из-за " виртуального шаблона...`:

#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.
}


В моем базовом классе мне нужно сказать: "реализуйте эти чистые абстрактные универсальные функции, которые принимают различные параметры разных типов и делают что-то в производных классах". И в моих производных классах мне нужно реализовать эти чистые абстрактные универсальные функции, которые принимают параметры разных типов.

Я не понимаю, как достичь этой функциональности, которую я хочу иметь(которую вы можете увидеть в приведенной выше программе, если она скомпилирована и работает). Пожалуйста, порекомендуйте решение или объясните, как это сделать.

Что я уже пробовал:

Я пытался сделать это с помощью виртуальных функций, но virtual не работает с шаблонами.

0x01AA

Вы не думали использовать может быть auto для параметров? Кажется, все гораздо проще. Извините, это тоже не работает. Та же проблема и с виртуальным...

Но Взгляните сюда, принятое решение. Может быть, это поможет:
c++ - шаблоны не могут быть " виртуальными’ - переполнение стека[^]

3 Ответов

Рейтинг:
4

KulaGGin

Вот версия поведения на языке 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<TStructure>(ICollection<byte> bytes);
        ICollection<byte> ToBytes<TStructure>(TStructure structure);
    };

    class ObjectTransformer1 : IObjectTransformer {

        #region Implementation of IObjectTransformerBaseAbstractClass

        public TStructure ToStructure<TStructure>(ICollection<byte> bytes) {
            throw new NotImplementedException();
        }

        public ICollection<byte> ToBytes<TStructure>(TStructure structure) {
            throw new NotImplementedException();
        }

        #endregion

    }

    class ObjectTransformer2 : IObjectTransformer {

        #region Implementation of IObjectTransformerBaseAbstractClass

        public TStructure ToStructure<TStructure>(ICollection<byte> bytes) {
            throw new NotImplementedException();
        }

        public ICollection<byte> ToBytes<TStructure>(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++ ленив: CRTP - ModernesCpp.com[^]

CRTP и статический полиморфизм. Наконец, C++ - версия поведения, которого я пытался достичь:
// bastAbstractTemplates.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include <vector>
#include <string>

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

    friend DerivedClass;
};

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

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

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

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

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


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

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

    int integer = 5;
    float someFloat = 9.79f;

    CoutStructureBytes(&objectTransformer1, integer);
    CoutStructureBytes(&objectTransformer2, someFloat);
}
PS я сам едва могу следовать этому коду, хотя и написал его, но думаю, что привыкну к нему.


Рейтинг:
2

Rick York

Вы слишком усложняете определение базового класса. Попробовать это :

using UCHAR		= unsigned char;
using VUCHAR	= std::vector< UCHAR >;

template< typename T >
class ObjectTransformerBase
{
public:
    virtual T ToStructure( VUCHAR & bytes ) = 0;
    virtual VUCHAR ToBytes( T & structure ) = 0;
};
Я решил написать тестовый класс и пару помощников и вот как они выглядят :
// some data to test with

struct SomeData
{
    int m_this { 101 };
    int m_that { 202 };
    int m_other { 303 };

    void Output( const char * msg )   // change trace to your output method of choice
    {
        trace( "%s\n", msg );
        trace( "m_this  is %6d\n", m_this );
        trace( "m_that  is %6d\n", m_that );
        trace( "m_other is %6d\n", m_other );
    }
};

using SomeDataObjectTransformer	= ObjectTransformerBase< SomeData >;

// helpers

template< typename T >
void CopyToItem( T & item, VUCHAR & vdata, size_t & count )
{
    memcpy( & item, & vdata[ count ], sizeof( T ) );
    count += sizeof( T );
}

template< typename T >
void CopyToVector( VUCHAR & vdata, T & item )
{
    UCHAR * pItem = (UCHAR *) & item;
    for( size_t n = 0; n < sizeof( T ); ++n )
        vdata.push_back( pItem[ n ] );
}

// this is the transformer class for SomeData

class SomeDataTransformer : public SomeDataObjectTransformer
{
public:
    virtual SomeData ToStructure( VUCHAR & vbytes )
    {
        SomeData data;
        size_t count = 0;
        CopyToItem( data.m_this, vbytes, count );
        CopyToItem( data.m_that, vbytes, count );
        CopyToItem( data.m_other, vbytes, count );
        return data;
    }

    virtual VUCHAR ToBytes( SomeData & data )
    {
        VUCHAR vbytes;
        CopyToVector( vbytes, data.m_this );
        CopyToVector( vbytes, data.m_that );
        CopyToVector( vbytes, data.m_other );
        return vbytes;
    }
};
Наконец, вот функция для проверки этого.
void TestObjectTransformer()
{
    SomeData data1;
    data1.Output( "data1 contents : " );

    SomeDataTransformer sdt;
    auto vecsdata = sdt.ToBytes( data1 );   // transform data to a vector

    // hack alert - cast the vector to a pointer to integers and offset the values

    int * pInts = (int *) & vecsdata[ 0 ];
    size_t vcount = vecsdata.size() / sizeof( int );
    for( size_t n = 0; n < vcount; ++n )
    {
        pInts[ n ] += 10000;
    }

    // copy the vector to some other data and display it

    SomeData data2 = sdt.ToStructure( vecsdata );
    data2.Output( "data2 contents :" );
}
вот как выглядит результат :
data1 contents :
m_this  is    101
m_that  is    202
m_other is    303
data2 contents :
m_this  is  10101
m_that  is  10202
m_other is  10303


Рейтинг:
0

KarstenK

Если вы делаете это таким образом, вам нужен один реальный класс где-то в вашей иерархии, который является базовым классом всех классов экземпляров для реализации. Лучше всего, когда этот класс имеет все интерфейсы и все виртуальные функции.

Используйте четкие и явные соглашения об именах, потому что иначе это может привести к беспорядку.


0x01AA

Я не думаю, что соглашения об именах здесь помогут...

KulaGGin

Я хочу иметь универсальные функции, которые работают с различными типами, наследованием, полиморфизмом и возможностью переопределять мои универсальные функции в производных классах. C++, в отличие от C#, не может сделать это с помощью обычных инструментов, таких как виртуальные методы, шаблоны функций, абстрактные классы, интерфейсы и наследование. Потому что нельзя смешивать виртуальное с шаблоном.
То, что я искал, - это CRTP и статический полиморфизм. Смотрите мое принятое решение с рабочими примерами в C# и C++, которые делают именно то, что мне нужно.