避免不同 类 中的代码重复,其中函数在 C++ 中执行相同的操作
avoiding code duplication in different classes where the function does the same in C++
我对 OOP 有点陌生,所以这个问题感觉有点奇怪,但我想知道在这种情况下我应该怎么做
假设我有一个 Tup4 class,它只包含 4 个双打,还有两个 classes Point4 和 Vec4 扩展了 Tup4。现在,检查 Tup4 中的相等性只是比较每个元组中的所有 4 个双精度数是否(大约)相等。这在扩展它的 classes 中都成立。但是,在 Tup4 中定义相等函数没有任何意义,因为这样我就可以检查 Point 和 Vector 之间的相等性,这没有多大意义。所以我不能在Tup4中定义一个virtual equals方法,那怎么办呢?两种情况下的代码完全相同,唯一的区别是函数的类型。所以我想知道我是否可以避免两种方法
bool equals(Point4 p);
bool equals(Vec4 v);
它们的作用相同但定义不同 classes
您可以为此使用模板。
将数学向量和点等类似值的类型硬塞进对象层次结构中实际上并不是很好地使用 OOP。对象层次结构意味着使用“引用语义”;而向量、元组和点希望成为值。
例如,请看how the C++ standard library implements complex numbers。它将它们实现为 class 模板,根据您要使用的数字类型进行参数化,例如float、double 等,然后重载算术运算符以处理 complex<T>
.
您将如何真正实现矢量等。class 类似。
Tup4 是一个概念,而不是 class。 Vec4 和 Point4 满足这个概念。
大部分 Vec4 和 Point4 都是作为模板实现的。
在极少数情况下,您需要以运行时多态方式处理 Tup4,不要使用继承,使用像 std 函数一样的类型擦除。但你可能不会。
struct Tup4Data{
double v[4];
};
template<class D>
struct Tup4Impl:Tup4Data{
// common implementation details of Tup4
// D is derived class (Vec4 or Point4)
};
struct Vec4:Tup4Impl<Vec4>{
// extra stuff for Vec4
};
struct Point4:Tup4Impl<Point4>{
// extra stuff for Poimt4
};
现在,只想处理原始双打而不关心的代码可以使用 Tup4Data
。 Tup4Impl
想查就用CRTP;这提供了 static 多态性。
关心是矢量还是点的可以选一个。
想要两者兼顾,行为不同的可以是模板代码,或者类型擦除。
最后一个案例——键入擦除——更难,但作为交换,你在其他所有案例中都会得到巨大的改进。而且 99% 的代码库甚至不需要键入擦除。
我什至不确定什么情况下有代码想在这里键入擦除。
所以别担心。 (如果您想学习,请查看示例标准函数实现)。
看起来你已经接受了一个答案,但这就是我要做的
我建议不要走模板路线:
在您的 Tup4 class 中定义一个相等方法,但保留它的保护:
class Tup4
{
public:
double a, b, c, d;
protected:
bool EqualityCheck(const Tup4& other) const
{
return (a == other.a && b == other.b && c == other.c && d == other.d);
}
};
然后你的 Point4
和 Vec4
classes 可以有重载的相等运算符调用父类的方法:
class Point4 : public Tup4
{
public:
bool operator==(const Point4& other) const
{
return EqualityCheck(other);
}
};
您希望 Point4 和 Vector4 这两种类型彼此不兼容,因为它们是不同的类型。现在,作为你自己,你需要 Tuple4 做什么。 Point4 是 Tuple4 真的很重要吗?换句话说,里氏替换原则在那里重要吗?我的猜测是,答案是 Tuple4 只是代码重用的一个方便的基类,而不是出于 OOP 的原因。
如果我的假设是正确的,使用私有基类会是更好的选择。由于基数是私有的,因此不允许比较 Vector4 和 Point4。为了方便代码复用,可以转发到基类实现:
class Point4: Tuple4 {
public:
bool operator==(Point4 const& rhs) const {
return static_cast<Tuple4 const&>(*this) == static_cast<Tuple4 const&>(rhs);
}
};
也就是说,考虑使用 std::array
作为基类而不是编写自己的基类。
我对 OOP 有点陌生,所以这个问题感觉有点奇怪,但我想知道在这种情况下我应该怎么做
假设我有一个 Tup4 class,它只包含 4 个双打,还有两个 classes Point4 和 Vec4 扩展了 Tup4。现在,检查 Tup4 中的相等性只是比较每个元组中的所有 4 个双精度数是否(大约)相等。这在扩展它的 classes 中都成立。但是,在 Tup4 中定义相等函数没有任何意义,因为这样我就可以检查 Point 和 Vector 之间的相等性,这没有多大意义。所以我不能在Tup4中定义一个virtual equals方法,那怎么办呢?两种情况下的代码完全相同,唯一的区别是函数的类型。所以我想知道我是否可以避免两种方法
bool equals(Point4 p);
bool equals(Vec4 v);
它们的作用相同但定义不同 classes
您可以为此使用模板。
将数学向量和点等类似值的类型硬塞进对象层次结构中实际上并不是很好地使用 OOP。对象层次结构意味着使用“引用语义”;而向量、元组和点希望成为值。
例如,请看how the C++ standard library implements complex numbers。它将它们实现为 class 模板,根据您要使用的数字类型进行参数化,例如float、double 等,然后重载算术运算符以处理 complex<T>
.
您将如何真正实现矢量等。class 类似。
Tup4 是一个概念,而不是 class。 Vec4 和 Point4 满足这个概念。
大部分 Vec4 和 Point4 都是作为模板实现的。
在极少数情况下,您需要以运行时多态方式处理 Tup4,不要使用继承,使用像 std 函数一样的类型擦除。但你可能不会。
struct Tup4Data{
double v[4];
};
template<class D>
struct Tup4Impl:Tup4Data{
// common implementation details of Tup4
// D is derived class (Vec4 or Point4)
};
struct Vec4:Tup4Impl<Vec4>{
// extra stuff for Vec4
};
struct Point4:Tup4Impl<Point4>{
// extra stuff for Poimt4
};
现在,只想处理原始双打而不关心的代码可以使用 Tup4Data
。 Tup4Impl
想查就用CRTP;这提供了 static 多态性。
关心是矢量还是点的可以选一个。
想要两者兼顾,行为不同的可以是模板代码,或者类型擦除。
最后一个案例——键入擦除——更难,但作为交换,你在其他所有案例中都会得到巨大的改进。而且 99% 的代码库甚至不需要键入擦除。
我什至不确定什么情况下有代码想在这里键入擦除。
所以别担心。 (如果您想学习,请查看示例标准函数实现)。
看起来你已经接受了一个答案,但这就是我要做的 我建议不要走模板路线:
在您的 Tup4 class 中定义一个相等方法,但保留它的保护:
class Tup4
{
public:
double a, b, c, d;
protected:
bool EqualityCheck(const Tup4& other) const
{
return (a == other.a && b == other.b && c == other.c && d == other.d);
}
};
然后你的 Point4
和 Vec4
classes 可以有重载的相等运算符调用父类的方法:
class Point4 : public Tup4
{
public:
bool operator==(const Point4& other) const
{
return EqualityCheck(other);
}
};
您希望 Point4 和 Vector4 这两种类型彼此不兼容,因为它们是不同的类型。现在,作为你自己,你需要 Tuple4 做什么。 Point4 是 Tuple4 真的很重要吗?换句话说,里氏替换原则在那里重要吗?我的猜测是,答案是 Tuple4 只是代码重用的一个方便的基类,而不是出于 OOP 的原因。
如果我的假设是正确的,使用私有基类会是更好的选择。由于基数是私有的,因此不允许比较 Vector4 和 Point4。为了方便代码复用,可以转发到基类实现:
class Point4: Tuple4 {
public:
bool operator==(Point4 const& rhs) const {
return static_cast<Tuple4 const&>(*this) == static_cast<Tuple4 const&>(rhs);
}
};
也就是说,考虑使用 std::array
作为基类而不是编写自己的基类。