我有一个任务来实现一个简单的SVG生成器。我需要支持Circle,Polyline和Text。这三种方法至少有4种常用方法: -SetStrokeColor -SetFillColor -SetStrokeWidth -ToString 主要要求之一是支持链接,例如: 折线{} .SetStrokeColor(“ white”)。SetFillColor(“ black”)...
我决定实现一个基类Element,所有其他类均从该基类继承。想法是拥有一个Document类,其中包含添加到文档中的所有元素的向量。 基本方法的样本签名:
// source of trouble
Element &SetStrokeColor(const Color &color) {
...
return *this;
}
我的派生类确实调用了这些方法,但是麻烦的是,这些方法返回对基类Element的引用,而不是派生类。
我的问题是,是否可以全部用c ++来实现??
答案 0 :(得分:4)
如果要共享实现和保留类型信息,则需要CRTP:
struct ElementBase { };
template <class Concrete>
struct Element : ElementBase {
Concrete &setStrokeWidth(int width) {
// Actual implementation...
(void) width;
return cthis();
}
private:
friend Concrete;
Element() = default;
Concrete &cthis() { return static_cast<Concrete &>(*this); }
Concrete &cthis() const { return static_cast<Concrete const &>(*this); }
};
struct Circle : Element<Circle> {
Circle &SetCircleCenter(int x, int y) {
// Actual implementation...
(void) x;
(void) y;
return *this;
}
};
int main() {
Circle c;
c.setStrokeWidth(4).SetCircleCenter(0, 0);
}
答案 1 :(得分:3)
使用协变返回类型,您可以
class Element {
public:
// ...
virtual Element& refToThis() { return *this; };
};
以及派生类
class Derived : public Element {
public:
// ...
Derived& refToThis() override { return *this; };
};
,当静态类型为Derived
时(例如,在Derived
本身中),您可以将Derived
实例作为Derived
实例处理。当静态类型为Element
时,refToThis()
的返回类型也为
答案 2 :(得分:0)
为进行比较:
class Base {};
class Derived : public Base {};
Derived d;
Base* p = &d; // points to a Base that in reality is a derived
Base& b = d; // in this respect, references do not differ...
// and you can get the original type back:
auto dr = static_cast<Derived&>(b); // if you know 100% for sure that b is a Derived
auto dp = dynamic_cast<Derived*>(p); // if p might point to another type; you get
// a null pointer back, if it does
返回指针或对this
/ *this
的引用绝对没有区别,所以是的,您可以放心地这样做。
编辑:
Circle().SetStrokeWidth(16).SetCircleCenter({0, 0})
。 SetStrokeWidth返回对Element的引用,因此SetCircleCenter不可用。
在这种情况下,您会遇到麻烦。就像lubgr denoted一样,您可以通过使用协变量返回类型覆盖来解决此问题–是的,这意味着您必须分别覆盖每个函数。另外,您可以使用CRTP保存所有这种方法:
template <typename T>
class SomeAppropriateName : public Element
{
public:
T& setStrokeWidth(unsigned int width)
{
Element::setStrokeWidth(width);
return static_cast<T&>(*this);
}
};
class Circle : public SomeAppropriateName<Circle>
{
// the overrides needed are inherited...
};
对于这种方法,Element
类中的原始函数必须是非虚拟的(实际上是一个优势,因为虚拟函数的调用成本更高...);在模板中,参数(T
,即Circle
尚未完全定义(使用CRTP模式),编译器将无法检查返回类型是否确实是协变的。所以实际上,我们只是在遮蔽基类的功能。
与覆盖相比,这仍然没有缺点:协变量返回类型仅在无论如何直接在对象上直接调用(虚拟)函数时才可用:
Circle c;
c.setStrokeWidth(7).setCenter();
Element& e = c;
e.setStrokeWidth(7).setCenter(); // fails, even if you implement co-variant override directly