考虑以下代码示例
#include <iostream>
using namespace std;
class Color
{
public:
virtual void mixColors(Color &anotherColor) = 0;
};
class RGB : public Color
{
public:
void mixColors(RGB &anotherColor);
};
void RGB::mixColors(RGB &kol)
{
return RGB(0xABCDEF);
}
我完全知道为什么这段代码不起作用( RGB 中的 mixColors()没有实现纯虚函数,因为它有不同的参数集)。但是,我想问一下是否有另一种方法可以解决这个问题。让我们说我想混合颜色,但对不同的颜色类使用不同的算法。我将不胜感激任何帮助。
答案 0 :(得分:3)
为什么你还需要一个虚拟方法呢?
如果参数是另一种RGB颜色,只有混合RGB
颜色才有意义,那么为什么应该有基因mixColor(Color)
方法。
如果你真的需要它,你可以覆盖并执行动态转换:
class RGB : public Color
{
public:
void mixColors(RGB &anotherColor);
void mixColors(Color &c) override { return mixColors(dynamic_cast<RGB&>(c)); }
};
void RGB::mixColors(RGB &kol)
{
return RGB(0xABCDEF);
}
这样,如果您尝试将RGB与不同类的颜色混合,您将在运行时获得异常。
答案 1 :(得分:3)
在继承需要子类型的语言中,例如C ++,您不能在派生类中使成员函数参数“更具体”。 (至少没有breaking type safety。)有关完整的技术说明,see here。
要更具体地理解这一点,请注意,在Color
类中,您声明存在具有签名的成员函数:
virtual void mixColors(Color &anotherColor) = 0;
这意味着任何颜色可以与任何其他颜色混合(不一定是同一类),并且此混合过程的具体实现仅取决于第一种颜色的类。这是完全错误的。
最简单的解决方案是简单地使用函数重载:
// I am assuming RGB and CMYK are cheap to pass by value, which seems reasonable.
// If this is not true, you can always pass them by const reference.
RGB mix_colors(RGB rgb1, RGB rgb2) { ... }
CMYK mix_colors(CMYK cmyk1, CMYK cmyk2) { ... }
或者,假设你真的想改变其中一种颜色,而不是产生一个新的颜色对象:
class RGB
{
// ...
public:
RGB & mix_colors(RGB); // return *this at the end
};
class CMYK
{
// ...
public:
CMYK & mix_colors(CMYK); // return *this at the end
};
使用重载而不是虚拟成员函数有一个缺点,但是:必须在编译时解析重载,而可以动态调度虚拟成员函数。遗憾的是,如果你需要对你想要混合的颜色执行运行时调度,那么你就有点紧张,因为C ++没有像Haskell的type classes或Common Lisp multi methods那样的东西。您可以使用visitor pattern对多个调度进行编码,但这显然不是很好。
答案 2 :(得分:2)
在这种情况下,您不需要纯虚函数。纯虚函数是函数的概念,无论如何都必须明确地重写。你想要的是一个普通的虚拟功能。
您希望传达一种颜色可以随时混合的概念。但是,您不希望保证始终实现此类功能。最好用普通的虚函数表示,它抛出异常:
class Color
{
public:
virtual ~Color(void) = 0;
virtual auto mix_colors(Color& color) -> Color&
{
throw std::logic_error("Cannot mix unrelated color schemes");
}
};
Color::~Color(void)
{ /* */ }
如果你完全不喜欢异常,你可以打印到std::cerr
或类似的东西。您也可以编写自己的异常并捕获它。
现在,您可以创建不同的颜色方案并重载此继承的虚拟成员函数:
class HSL; class HSV; class RGB;
class RGB: public Color
{ /* */ };
class HSV: public Color
{
public:
auto mix_colors(HSL& color) -> HSV&
{
std::cout << "Mixing HSV with HSL" << '\n';
return *new HSV{};
}
};
class HSL: public Color
{
public:
auto mix_colors(HSV& color) -> HSL&
{
std::cout << "Mixing HSL with HSV" << '\n';
return *new HSL{};
}
};
这里我有三种颜色方案RGB,HSV和HSL。当然它们可以相互混合,但是现在我只实现了从HSL到HSV的转换成员函数,反之亦然。
现在我可以这样:
int main(void)
{
RGB rgb{};
HSV hsv{};
HSL hsl{};
HSV new_hsv{hsv.mix_colors(hsl)}; // Mixing HSV with HSL
HSL new_hsl{hsl.mix_colors(hsv)}; // Mixing HSL with HSV
RGB new_rgb{rgb.mix_colors(hsv)}; // terminating with uncaught exception of type std::logic_error: Cannot mix unrelated color schemes Abort trap: 6
}
答案 3 :(得分:1)
RGB需要能够与任何颜色混合。继承Color强加了对RGB的要求。
如果RGB想要添加一种特殊的方式来混合特殊种类的颜色,那么它除了通常的方式之外还会这样做。您可以实现两个函数并让编译器在编译时选择,或者您可以实现所需的函数并在运行时测试其参数的类型。后者的代码可以是if(dynamic_cast<RGB*>(&anotherColor)) { ... } else { ... }
。
答案 4 :(得分:1)
您还可以将混音分离为混音器类,并通过实现转换构造函数来定义不同颜色类型之间的转换。那些不能相互转换的颜色(它们不支持转换构造函数)不能混合,这会导致编译时错误。
以下是例子:
#include<iostream>
class Color {};
class CMYK;
class RGB : public Color
{
public:
RGB() = default;
RGB(const RGB&) = default;
RGB(const CMYK& c)
{
}
};
class CMYK : public Color
{
public:
CMYK() = default;
CMYK(const CMYK&) = default;
CMYK(const RGB& r)
{
}
};
class colorMixer
{
public:
RGB mixColors(RGB r1, RGB r2)
{
std::cout << "mixing RGB , RGB" << std::endl;
return RGB();
}
CMYK mixColors(CMYK c1, CMYK c2)
{
std::cout << "mixing CMYK, CMYK" << std::endl;
return CMYK();
}
CMYK mixColors(CMYK c1, RGB r1)
{
// Convert RGB to CMYK
CMYK c2(r1);
return mixColors(c1, c2);
}
RGB mixColors(RGB r1, CMYK c1)
{
// Convert CMYK to rgb.
RGB r2(c1);
return mixColors(r1, r2);
}
};
int main(int argc, const char *argv[])
{
CMYK c1,c2;
RGB r1,r2;
colorMixer m1;
m1.mixColors(c1,c2);
m1.mixColors(c1,r1);
m1.mixColors(r1,c1);
m1.mixColors(r1,r2);
return 0;
}