Croper 发表于 2020-1-6 20:56:21

使用接口(基类)访问模板类中的数据怎么写才漂亮?

本帖最后由 Croper 于 2020-1-6 21:31 编辑

本来以为很简单一个问题。。突然发现我纠结到了。

比如说这里有一个模板类A:template <typename T>
class A{
public:
        //....其他数据成员

        T data;//某个数据成员

        //..其他成员函数
};


为了方便访问它,写了一个接口类,并让模板类继承://接口类
struct IA {

        //virtual IA* copy() =0;
        //virtual int id() =0;
        //...其他接口..blalblabla..
};

//模板类
template <typename T>
class A :public IA {
public:
        //....其他数据成员

        T data;//某个数据成员

        //..其他成员函数
};

这个时候,我想通过IA来访问类A中的数据成员data。
类A是模板类,那么这个接口只能写成模板函数。。。
但是模板函数是不能写成虚函数的,想来想去,只想出两种方法:

//接口类
struct IA {
private:
        virtual void* _GetData() = 0;//返回类型改为void*,这样能写成虚函数
public:
        template <typename T>          //然后再包装一下
        T GetData() {
                return *static_cast<T*>(_GetData());
        }
};

//模板类
template <typename T>
class A :public IA {
public:
        T data;//需要的数据成员
        void* _GetData() override {   //这是继承的函数
                return &data;
        }
};
这样写的问题是,即使模板函数GetData的类型和模板类不一致且无法转化,也不会报错,
而且类型之间的互相转化也是直接二进制数据强制转化,这肯定不是所期望的

//接口类
struct IA {
public:
        template <typename T>
        T GetData();
};

//模板类
template <typename T>
class A :public IA {
public:
        T data;//需要的数据成员
};

template<typename T>
T IA::GetData()
{
        return dynamic_cast<A<T>*>(this)->data;//将this指针转化为A<T>的指针
};
这样首先是不符合设计模式了,抽象类(底层)依赖具体实现(高层)。
另外,同样无法做到double与int的相互转化。

所以还有什么好点的写法么。。?


人造人 发表于 2020-1-6 20:56:22

我仔细想了想,这个问题应该无解,至少我无解
这个问题要求C++能够记住类型信息,也就是要求C++能够正确读取 void *
试想一下,在new一个对象的时候保存这个对象的类型,然后把new出来的对象给void 指针保存,在使用的时候用之前保存的类型信息对void指针解引用
但是C++好像不能读取void *,我百度不到如何保存类型信息

bin554385863 发表于 2020-1-6 21:06:48

看不懂你想表达什么意思

Croper 发表于 2020-1-6 21:58:28

就是我现在想通过接口类访问模板类中的数据data,
但是找不到一个好的实现方法能满足要求:
1、接受的变量和模板类的类型不匹配且无法相互转化时能报错(就像你给一个string赋一个vector时会报错一样)
2、最好接受的变量和模板类的能互相转化的时候时能正常转化(比如一个double类型的变量能够接受一个整数并隐式转化)
3、符合一般来说面向对象的设计原则。

人造人 发表于 2020-1-7 00:34:32

"类A是模板类,那么这个接口只能写成模板函数。。。
但是模板函数是不能写成虚函数的"

我不明白你说的这句话是什么意思
通过IA来访问类A中的数据成员data
可以在IA中加一个纯虚函数呀,不过IA也要用模板
写成下面这样,是你想要的吗?

#include <iostream>

//接口类
template <typename T>
struct IA {
public:
        virtual IA* copy() =0;
        virtual uint64_t id() =0;
        //...其他接口..blalblabla..

        virtual T GetData() = 0;
};

//模板类
template <typename T>
class A:public IA<T> {
public:
        IA<T>* copy() {
                return new A<T>(*this);
        }
        uint64_t id() {
                return uint64_t(this);
        }
        T GetData() {
                return this->data;
        }
        //...其他接口..blalblabla..

        //....其他数据成员

        T data;//某个数据成员

        //..其他成员函数
};

int main()
{
        A<double> a;

        return 0;
}

人造人 发表于 2020-1-7 00:50:54

#include <iostream>
#include <string>

//接口类
template <typename T>
struct IA {
public:
        virtual IA* copy() =0;
        virtual uint64_t id() =0;
        //...其他接口..blalblabla..

        virtual T GetData() = 0;
};

//模板类
template <typename T>
class A:public IA<T> {
public:
        A(const T &n): data(n) {
        }

        IA<T>* copy() {
                return new A<T>(*this);
        }
        uint64_t id() {
                return uint64_t(this);
        }
        T GetData() {
                return this->data;
        }
        //...其他接口..blalblabla..

        //....其他数据成员

        T data;//某个数据成员

        //..其他成员函数
};

int main()
{
        IA<double> *a = new A<double>(123.45);
        IA<int> *b = new A<int>(123456);
        IA<std::string> *c = new A<std::string>("abcd");
        std::cout << "id: " << a->id() << std::endl;
        std::cout << "data: " << a->GetData() << std::endl;
        std::cout << "id: " << b->id() << std::endl;
        std::cout << "data: " << b->GetData() << std::endl;
        std::cout << "id: " << c->id() << std::endl;
        std::cout << "data: " << c->GetData() << std::endl;

        std::cout << std::endl;

        IA<double> *na = a->copy();
        IA<int> *nb = b->copy();
        IA<std::string> *nc = c->copy();
        std::cout << "id: " << na->id() << std::endl;
        std::cout << "data: " << na->GetData() << std::endl;
        std::cout << "id: " << nb->id() << std::endl;
        std::cout << "data: " << nb->GetData() << std::endl;
        std::cout << "id: " << nc->id() << std::endl;
        std::cout << "data: " << nc->GetData() << std::endl;
        delete na;
        delete nb;
        delete nc;

        return 0;
}


id: 2660434001472
data: 123.45
id: 2660433999552
data: 123456
id: 2660433984144
data: abcd

id: 2660434001952
data: 123.45
id: 2660434000192
data: 123456
id: 2660433983136
data: abcd
请按任意键继续. . .

bin554385863 发表于 2020-1-7 12:42:12

#include <iostream>
#include <sstream>
class A_base
{
    private:
      int a;
    public:
      A_base(const int &b = 0):a(b){}
      void showdata() const
      {
            std::cout << a;
      }
      virtual ~A_base() { std::cout << "调用A_base成功" << std::endl; }
};
template <typename T>
class A : public A_base
{
    private:
      T b;
    public:
      A(const T c = 0) : b(c), A_base(int(c)){}
      void showdata()
      {
            std::cout << "基类数据: ";
            A_base::showdata();
            std::cout << std::endl;
            std::cout << "子类数据: " << b << std::endl;
      }
      ~A() { std::cout << "调用A成功" << std::endl; }
};
int main(int argc, char const *argv[])
{
    A<float> a = 3.014159;
    a.showdata();
    std::cout << std::endl;
    return 0;
}

====================================================
Microsoft Windows [版本 10.0.18363.535]
(c) 2019 Microsoft Corporation。保留所有权利。

E:\Users\admin\Documents\VScode\Code>c:\Users\admin\.vscode\extensions\ms-vscode.cpptools-0.26.2\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-vtfeiqkt.yjn --stdout=Microsoft-MIEngine-Out-txf35s5p.zl2 --stderr=Microsoft-MIEngine-Error-w31u0pzy.i0y --pid=Microsoft-MIEngine-Pid-meui1qcz.std --dbgExe=D:\MinGW\bin\gdb.exe --interpreter=mi
基类数据: 3
子类数据: 3.01416

调用A成功
调用A_base成功

E:\Users\admin\Documents\VScode\Code>

人造人 发表于 2020-1-7 13:12:11

bin554385863 发表于 2020-1-7 12:42
====================================================
Microsoft Windows [版本 10.0.18363.535]
(c) ...

在C++中没有Java中的接口概念,抽象类可以模拟Java中的接口。

用抽象类模拟接口:抽象类中只有纯虚函数,没有成员变量和其他函数。

https://blog.csdn.net/qq_41093451/article/details/79645435

我不记得在哪里看过一句话
“C++中,一个类中如果只有纯虚函数,那么这个类就是接口”
这很可能不是原话,我就是这样理解的


“在C++中,含有纯虚拟函数的类称为抽象类”
也就是说含有纯虚函数的类是抽象类,如果一个类中只有纯虚函数,那就是接口
https://baike.so.com/doc/6213661-6426933.html

Croper 发表于 2020-1-7 14:04:35

我大概还是没说清楚。。
我想要的是这种效果:IA *p = new A<int>(5);
cout << p->GetData<int>() << endl;       //输出5
p = new A<string>("fishc.com")
cout << p->GetData<string>() << endl;    //输出fishc.com

string s = p->GetData<string>();         //正确赋值
int i = p->GetData<int>();               //报错:string没有默认的转化为int的函数

Croper 发表于 2020-1-7 14:15:08

本帖最后由 Croper 于 2020-1-7 14:17 编辑

中午又想出两个办法,基本解决,但还是感觉有点绕:

方法1,引入一个中间模板类,让A去继承这个中间模板类#include <string>
#include <iostream>
using namespace std;

//中间类
template <typename T>
struct IA_templ {
        virtual T GetData() = 0;
};


//接口类
struct IA {

public:
        template <typename T>         
        T GetData() {
                return dynamic_cast<IA_templ<T>*>(this)->GetData();
        }
        virtual ~IA() {};
};


//模板类
template <typename T>
class A :public IA,public IA_templ<T> {   
        T data;//需要的数据成员
public:
        A(const T& t) :data(t) {};
        T GetData() override {   //这是继承的函数
                return data;
        }
};

int main() {
        IA *p = new A<int>(5);
        cout << p->GetData<int>() << endl;       //输出5
        p = new A<string>("fishc.com");
        cout << p->GetData<string>() << endl;    //输出fishc.com

        string s = p->GetData<string>();         //正确赋值
        int i = p->GetData<int>();               //报错:string没有默认的转化为int的函数
        system("pause");
}
大概符合要求吧,虽然这个接口类已经不是"接口类"了。。。

人造人 发表于 2020-1-7 15:06:32

Croper 发表于 2020-1-7 14:15
中午又想出两个办法,基本解决,但还是感觉有点绕:

方法1,引入一个中间模板类,让A去继承这个中间模板 ...

你为什么非要在GetData后面指定类型?
int i = p->GetData<int>();

人造人 发表于 2020-1-7 15:07:47

Croper 发表于 2020-1-7 14:15
中午又想出两个办法,基本解决,但还是感觉有点绕:

方法1,引入一个中间模板类,让A去继承这个中间模板 ...

p = new A<string>("fishc.com");

在创建出这个对象的时候已经指定过了呀

Croper 发表于 2020-1-7 15:13:57

人造人 发表于 2020-1-7 15:06
你为什么非要在GetData后面指定类型?
int i = p->GetData();

因为返回类型不同啊。。C++又没有办法根据返回类型重载。。。

Croper 发表于 2020-1-7 15:16:44

方法二是想自己做虚函数表的。。。。
但是发现成员函数指针的转换不是一般地让人头大。。。研究中。。。

Croper 发表于 2020-1-7 15:17:48

bin554385863 发表于 2020-1-7 12:42
====================================================
Microsoft Windows [版本 10.0.18363.535]
(c) ...

你这根本就不是重载啊- -

人造人 发表于 2020-1-7 16:22:31

Croper 发表于 2020-1-7 15:13
因为返回类型不同啊。。C++又没有办法根据返回类型重载。。。

我明白你的意思了,我研究研究

bin554385863 发表于 2020-1-7 18:06:10

Croper 发表于 2020-1-7 15:17
你这根本就不是重载啊- -

....你不是只要能访问数据就可以了吗

人造人 发表于 2020-1-7 18:17:11

bin554385863 发表于 2020-1-7 18:06
....你不是只要能访问数据就可以了吗

他在玩设计模式

bin554385863 发表于 2020-1-7 18:19:52

(⊙o⊙)

人造人 发表于 2020-1-7 18:31:16

这样可以吗?
你既然想在GetData后面指定类型,那为什么不能在GetData后面先加.access然后再加类型
GetData后面不能加类型,因为GetData函数不能用模板

写这个代码的时候遇到了一些概念上的问题
class IA
{
public:
        virtual ~IA() = 0;
        virtual uint64_t id() = 0;
        virtual data_t get_data() = 0;
};

// 没办法,子类要求这个函数
IA::~IA()
{
}


子类要求IA有一个析构函数,IA中的函数必须都是纯虚函数
子类还要求这个析构函数有实现代码,然后就写成了上面这样
这样的IA还能称得上是接口吗?我百度不到结果

#include <iostream>
#include <string>

class data_t
{
public:
        template <typename T>
        data_t(const T &n, void (*free_pointer)(void *)): value(new T(n)), free_pointer(free_pointer)
        {
        }
        ~data_t()
        {
                // 当前类不知道这个指针指向的类型,无法删除这个指针,让创建者(创建当前对象的代码)来删除
                // 我尝试过在当前类中保存指针的类型,但是失败了
                this->free_pointer(this->value);
        }
        template <typename T>
        T access()
        {
                return *static_cast<T *>(this->value);
        }

private:
        void *value;
        void (*free_pointer)(void *p);
};

class IA
{
public:
        virtual ~IA() = 0;
        virtual uint64_t id() = 0;
        virtual data_t get_data() = 0;
};

// 没办法,子类要求这个函数
IA::~IA()
{
}

template <typename T>
class A: public IA
{
public:
        A(const T &n): data(n) {}
        uint64_t id()
        {
                return uint64_t(this);
        }
        data_t get_data()
        {
                return data_t(data, this->free_pointer);
        }
private:
        T data;

        static void free_pointer(void *p)
        {
                delete static_cast<T *>(p);
        }
};

int main(void)
{
        IA *p = new A<int>(5);
        std::cout << p->id() << std::endl;
        std::cout << p->get_data().access<int>() << std::endl;

        delete p;
        p = new A<std::string>("fishc.com");
        std::cout << p->id() << std::endl;
        std::cout << p->get_data().access<std::string>() << std::endl;

        std::string s = p->get_data().access<std::string>();
        //int i = p->get_data().access<std::string>();
        delete p;

        return 0;
}
页: [1] 2
查看完整版本: 使用接口(基类)访问模板类中的数据怎么写才漂亮?