假设您有如下内容:
class Shape // base class
{
private:
bool degenerate, ill_defined;
...
public:
bool isVoid () { return false; }
bool isCircle() { return false; }
bool isPoint () { return false; }
bool isPlane () { return false; }
bool isSphere() { return false; }
...
};
class Void : public Shape {
...
}
class Plane : public Shape
{
public:
bool isPlane() { return !degenerate && !ill_defined; }
bool isVoid () { return ill_defined; }
...
operator Void () throw() {
if (isVoid()) return Void();
else throw ...; //some error
}
...
}
class Point : public Shape {
private:
double radius;
...
public:
bool isPoint() { return !ill_defined; }
bool isVoid () { return ill_defined; }
...
operator Void () throw() { ... }
...
}
class Circle : public Shape // similar to the rest
class Sphere : public Shape // similar to the rest
Plane
之间的交集和一个 Sphere
可以是
- 一个
Circle
(如果平面“穿过”球体) - 一个
Point
(如果平面“刚刚接触”球体) - 一个
Void
(如果球体完全位于平面上方或下方)
我想知道如何最好地定义和使用 Plane
之间的交集和一个 Sphere
,由于假设的返回类型
intersect(const Sphere& S, const Plane& P)
方法/自由函数在编译时未知。
我以前从未遇到过这种情况,所以我查找了一些可能的方法来做到这一点。我遇到了this question其中推荐boost::variant
。在我的情况下,这看起来像
boost::variant<Void, Point, Circle> intersection =
intersect(const Sphere& S, const Plane& P);
但这有三个缺点:
- 太丑了。
类似于
intersection.radius
不能按原样使用,因为Point
和Void
没有radius
。你必须做类似的事情if (intersection.isPoint()){ ... } else if (intersection.isCircle()) { // possibly cast to Point if degenerate, otherwise: double R = intersection.radius; ... } // etc.
实现所有这些形状的库的用户始终必须知道两个形状相交可以返回什么类型。也就是说,用户总是必须声明
boost::variant<scope::Void, scope::Point, scope::Circle>
类型的内容。这很复杂而且很丑陋。幸运的是,c++11 有auto
的关键字。或者,您可以使用像这样的成员class Sphere : public Shape { ... public: boost::variant<scope::Void, scope::Point, scope::Circle> intersect_type; intersect_type intersect(const Plane& P); ... };
这样我们就可以使用
Sphere::intersect_type t = S.intersect(P);
哪里
S
是Sphere
的一个实例和P
Plane
的一个实例。但我们仍然必须单独处理所有可能的类型:if (intersection.isPoint()){ ... } else if (intersection.isCircle()){ intersection.radius; } // etc.
因此我们试图从用户那里消除的复杂性实际上仍然存在。
我觉得我错过了一些东西。也许有一种更聪明的方法来实现我的Shape
基类?或者我应该创建一个单独的专用Intersect
类(class)?对于这种情况,什么解决方案是最优雅、最高效、最有效的?
最佳答案
副手:
isXXXX()
对我来说,谓词方法似乎有一种代码味道。你会这样做
-
if (dynamic_cast<Circle*>(shapePtr))
通常与 RTTI - 或者使用
variant::which()
和/或variant::type()
区分变体的存储值
对于你的问题:
有几种可能的方法。
经典的面向对象方法是从 Shape 派生所有内容并始终返回
std::unique_ptr<Shape>
(或类似)。但是,显然,您可以执行现代 C++ 静态 OO,在这种情况下,您最终会得到类似于变体的东西。然后,您可以编写一个访问者来处理不同的情况:
(直播于 http://liveworkspace.org/code/bad329cb40d94a21531e1153f4c0877b )
#include <string>
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>
struct Shape
{
/*virtual*/ double getSurface() const { return 42.0; } // TODO
};
struct Circle : Shape {};
struct Point : Shape {};
struct Rect : Shape {};
struct Nil {};
typedef boost::variant<Nil, Circle, Point, Rect> Intersect;
struct DescribeVisitor : boost::static_visitor<std::string>
{
std::string operator()(Circle const& s) const {
return std::string("Got a circle of ") + boost::lexical_cast<std::string>(s.getSurface());
}
std::string operator()(Rect const& s) const {
return std::string("Got a rectangle of ") + boost::lexical_cast<std::string>(s.getSurface());
}
std::string operator()(Point const& s) const {
return std::string("Got a point of ") + boost::lexical_cast<std::string>(s.getSurface()); // mmm bit funny :)
}
std::string operator()(Nil const&) const {
return std::string("Got an empty intersection");
}
};
std::ostream& operator<<(std::ostream& os, Intersect const& i)
{
return os << boost::apply_visitor(DescribeVisitor(), i);
}
int main(int argc, const char *argv[])
{
Intersect describe = Point();
std::cout << describe << std::endl;
describe = Rect();
std::cout << describe << std::endl;
describe = Circle();
std::cout << describe << std::endl;
}
输出:
Got a point of 42
Got a rectangle of 42
Got a circle of 42
关于c++ - 变量返回类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12705506/