如何解决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实例...然后调用适当的函数对该类型进行操作。
要实现类型擦除,您有两个基本选项:
-
在MyNumber的内部,它将存储指向MyInteger或MyRational的指针。这会增加一个间接层,并且可能会影响您的缓存,因此对于数字类型之类的东西,可能是不可取的。
-
使用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 举报,一经查实,本站将立刻删除。