鱼C论坛

 找回密码
 立即注册
查看: 2048|回复: 32

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

[复制链接]
发表于 2020-1-6 20:56:21 | 显示全部楼层 |阅读模式
100鱼币
本帖最后由 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 *,我百度不到如何保存类型信息

最佳答案

查看完整内容

我仔细想了想,这个问题应该无解,至少我无解 这个问题要求C++能够记住类型信息,也就是要求C++能够正确读取 void * 试想一下,在new一个对象的时候保存这个对象的类型,然后把new出来的对象给void 指针保存,在使用的时候用之前保存的类型信息对void指针解引用 但是C++好像不能读取void *,我百度不到如何保存类型信息
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-1-6 20:56:22 | 显示全部楼层    本楼为最佳答案   
我仔细想了想,这个问题应该无解,至少我无解
这个问题要求C++能够记住类型信息,也就是要求C++能够正确读取 void *
试想一下,在new一个对象的时候保存这个对象的类型,然后把new出来的对象给void 指针保存,在使用的时候用之前保存的类型信息对void指针解引用
但是C++好像不能读取void *,我百度不到如何保存类型信息
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-1-6 21:06:48 | 显示全部楼层
看不懂你想表达什么意思
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2020-1-6 21:58:28 | 显示全部楼层
就是我现在想通过接口类访问模板类中的数据data,
但是找不到一个好的实现方法能满足要求:
1、接受的变量和模板类的类型不匹配且无法相互转化时能报错(就像你给一个string赋一个vector时会报错一样)
2、最好接受的变量和模板类的能互相转化的时候时能正常转化(比如一个double类型的变量能够接受一个整数并隐式转化)
3、符合一般来说面向对象的设计原则。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 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;
}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 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
请按任意键继续. . .
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 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>  
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 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
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 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的函数
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 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");
}
大概符合要求吧,虽然这个接口类已经不是"接口类"了。。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-1-7 15:06:32 | 显示全部楼层
Croper 发表于 2020-1-7 14:15
中午又想出两个办法,基本解决,但还是感觉有点绕:

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

你为什么非要在GetData后面指定类型?
int i = p->GetData<int>();
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-1-7 15:07:47 | 显示全部楼层
Croper 发表于 2020-1-7 14:15
中午又想出两个办法,基本解决,但还是感觉有点绕:

方法1,引入一个中间模板类,让A去继承这个中间模板 ...
p = new A<string>("fishc.com");

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

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2020-1-7 15:13:57 | 显示全部楼层
人造人 发表于 2020-1-7 15:06
你为什么非要在GetData后面指定类型?
int i = p->GetData();

因为返回类型不同啊。。C++又没有办法根据返回类型重载。。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2020-1-7 15:16:44 | 显示全部楼层
方法二是想自己做虚函数表的。。。。
但是发现成员函数指针的转换不是一般地让人头大。。。研究中。。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2020-1-7 15:17:48 | 显示全部楼层
bin554385863 发表于 2020-1-7 12:42
====================================================
Microsoft Windows [版本 10.0.18363.535]
(c) ...

你这根本就不是重载啊- -
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-1-7 16:22:31 | 显示全部楼层
Croper 发表于 2020-1-7 15:13
因为返回类型不同啊。。C++又没有办法根据返回类型重载。。。

我明白你的意思了,我研究研究
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-1-7 18:06:10 | 显示全部楼层
Croper 发表于 2020-1-7 15:17
你这根本就不是重载啊- -

....你不是只要能访问数据就可以了吗
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-1-7 18:17:11 | 显示全部楼层
bin554385863 发表于 2020-1-7 18:06
....你不是只要能访问数据就可以了吗

他在玩设计模式
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-1-7 18:19:52 From FishC Mobile | 显示全部楼层
(⊙o⊙)
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 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;
}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-1-16 08:22

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表