C ++:返回可以是特殊派生类的对象

如何解决C ++:返回可以是特殊派生类的对象

很抱歉,但我无法在没有介绍的情况下提出这个问题。

让我们假设必须实现一个“数字”(class CNum的对象),该数字可以是(带符号的)整数(∈ℤ)或有理数(∈ℚ)。 想象有一个成员函数prn来打印数字。当然,我们希望以不同的方式打印整数和有理数。

典型的“ 旧样式”(C / ish)实现如下:

class CNum
{
 public:
  char type; // In this example,'Q' or 'Z'.
  [...]
  char *prn(char *s)
   {
    if(type=='Z') sprintf(s,"something...."); else
    if(type=='Q') sprintf(s,"something else ...");
    return s;
   }
  [...]
};

更多“ 现代”(C ++ / ish)方法倾向于使用派生类和虚函数。 所以我会写类似的东西:

class CNum
{
 public:
  [...]
  virtual char *prn(char *s) {....};
};

class ZZ : public CNum
{
  long int n; // n is the value.
  // implementation for integers,with proper prn()
};

class QQ : public CNum
{
  long int a; unsigned long int b; // a/b is the value.
  // implementation for rationals,with proper prn()
};

...而且看起来好多了(不是吗?)。

好的...现在,我需要代码将一个整数除以另一个。 通常,两个整数之间的比率是有理数,所以我会这样写:

QQ operator / (ZZ &x,ZZ &y)
{
 QQ R;
 long int j;

 if(y.n<0) {R.a=-x.n; R.b=-y.n;}
 else      {R.a= x.n; R.b= y.n;}
 for(j=2; j<=abs(R.a) && j<=R.b; j++) while(!(R.a%j) && !(R.b%j)) {R.a/=j; R.b/=j;}
 if(R.b==1) ; // (it's a whole number,stored as rational)(sorry...)
 return R;
}

最长的行(for(j...)可以使例如结果为2/3而不是2006/3009

因此,出现了一个实际问题:在class ZZ的情况下,如何返回类型为class QQ(而不是if(R.b==1))的对象? 在“ 旧样式”方法中,这将是微不足道的。但这意味着所有成员函数都应该是if(type==...){...} else if(type==...){...} else ...的集合,而不是我想要维护的。

专家们会提出什么建议?

(我已经找到了一种返回对新对象的引用的解决方案,但是比问题更严重,否则会泄漏内存)

还:如果有人理解了这个问题(也不知道答案):有没有更好的标题建议?

提前坦克...

解决方法

根据SO的建议,避免回答评论中的问题。但是我没有提供完整的答案...所以这介于评论和答案之间。

您面临的问题是QQ和ZZ是大小不同的不同对象。值得注意的是,其中一个具有附加的成员变量(无符号long int b)。

因此,运算符/不能根据上下文返回两个值,因为它们不兼容。它们的类型不同,但大小也不同。

基本上,这里的解决方案是类型擦除。本质上,您想创建一个类型,可以说是MyNumber。它是一个在其中存储MyInteger或MyRational类型的容器。

这意味着对MyNumber进行的每个操作都必须进行运行时测试,以查看它是在其中存储MyInteger实例还是MyRational实例...然后调用适当的函数对该类型进行操作。

要实现类型擦除,您有两个基本选项:

  1. 在MyNumber的内部,它将存储指向MyInteger或MyRational的指针。这会增加一个间接层,并且可能会影响您的缓存,因此对于数字类型之类的东西,可能是不可取的。

  2. 使用Use Union,因为在编译时已知MyNumber的所有可能表示形式。这样就可以将它们存储在堆栈上,并且从总体上讲,您可以更轻松地确保一系列MyNumber实例中包含的数据并排放置。

通过Google进行快速搜索即可发现其中一个完整的类型擦除示例: https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Erasure

该实现在内部使用unique_ptr,因此它遵循分配路线。您可能需要查找并查看联合的用法。

编辑:

很显然,自从我完成C ++以来,已经太久了!您可以使用std :: variant 作为联合,而不是进行联合,而这可能是一堆开始深入您可能不熟悉的语言使用的工作。

那会是这样的:

class MyNumber {
  public:
    MyNumber( MyInteger i )...
    MyNumber( MyRational r )...

    MyNumber operator / (MyNumber const& other) const {
      ... internally use the DivideVisitor struct with std::visit ...
    }

  private:
    struct DivideVisitor {
      ... look up how std::visit works with std::variant ...
      ... and the implementation for this will become clear ...
    };

    std::variant< MyInteger,MyRational > m_internal;
};
,

您正在询问任何强类型语言的常见问题。问题在于,在编译时指定了函数结果,尤其是其类型,但是仅在运行时才知道函数参数。为了解决这个问题,您必须使用多态性。

您称为“旧C风格”的解决方案将转换为C ++,如下所示:

std::unique_ptr<CNum> operator/ (ZZ const& x,ZZ const& y)
{
    //calculate "result = x/y"
    //if rational
    return std::make_unique<QQ>(result);
    //else if whole number
    return std::make_unique<ZZ>(result);
}

使用此方法,您将返回一个指向有理数或整数的唯一指针-然后可以将其用于进一步的计算中,该计算也需要CNum

此外,如果您想知道确切的名称,可以使用dynamic_cast

ZZ a;
ZZ b;
auto res = a/b;
if(QQ* d = dynamic_cast<QQ*>(res.get()))
{
     //result is rational
}
else if(ZZ* d = dynamic_cast<ZZ*>(res.get()))
{
     //result is whole
}

但是,您应该很少需要它。

此外,还有其他选择。例如,您可以实现一个虚函数std::string type(),该虚函数返回派生的有理类的"Q"和整数的"Z",从而替换C-中的char type变量样式解决方案。

总结:旧的C风格几乎从来没有比现代C ++风格更好。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-