使用多态性时在子对象之间进行转换?

Convert between child objects when using polymorphism?

本文关键字:之间 转换 对象 多态性      更新时间:2023-10-16

我有一个问题可能与我的设计有关,但我想要有关如何改进它的建议。 本质上,我有一个父类,它有几个子类,我需要能够在子类之间进行转换。但是,在当前设计中要转换的对象使用多态性和父类指针。我这样做是因为最终使用哪个子类由用户输入决定。我想出了三种方法来解决这个问题:

  1. 实现一个单独的"转换器"类,该类可以获取每个子类并将其转换为另一个子类。
  2. 声明将每个子类转换为其他子类的虚函数。(这将创建循环依赖关系,我认为这不是一个好主意......
  3. 对象中包含枚举数据成员,说明它们是什么类型,以便我可以在转换时使用 switch() 语句。

我应该考虑其他方式吗? 这是一些我认为显示我想做什么的代码。

class Rotation
{
public:
void  Set();
Vector Get();
virtual void Rotate(float amount);
virtual void SetFromQuaternion(Vector t_quaternion);
private:
Vector m_rotation;
}
class EulerAngles : Rotation
{
public:
void Rotate(float t_amount);
void SetFromQuaternion(Vector t_quaternion);
}
class Quaternion: Rotation
{ 
public:
void Rotate(float t_amount);
void SetFromQuaternion(Vector t_quaternion);//Just copies data
}
class RigidBody
{
public:
RigidBody(Rotation *t_rotation);
Rotation GetRotation();
void SetRotationFromQuaternion(Vector t_rotation) {m_rotation->SetRotationFromQuaternion(t_rotation);}
private:
std::unique_ptr<Rotation> *m_rotation;
}
int main()
{
//Argument is based on user input file, but euler angles are used as an example
Rigidbody body_1 = RigidBody(new EulerAngles());
// I want to rotate using quaternions to avoid singularities, but then convert back to euler angles. So here's where I would convert. How should I do this?
Quaternion temp_quaternion = (Quaternion)body_1.GetRotation();
temp_quaternion.Rotate(amount);
body_1.SetRotationFromQuaternion(temp_quaternion.Get());
return;
}

请注意,我的实际代码更复杂。我的问题更多地与整体设计"最佳实践"有关。提前感谢!

如果使用多态性,您唯一能做的就是用dynamic_cast取回原始类型。例:

Rotation * r = new Quaternion;
Quaternion * q = dynamic_cast<Quaternion*>(r);

现在,如果您想将一个子类转换为另一个子类,我想您已经知道dynamic_cast会失败,因为内部类型不是EulerAngles而是Quaternion

要做到这一点,据我所知,你别无选择,你必须自己转换它。换句话说,编写一个转换器函数。

添加数据成员以提供对象类型的想法非常好,但这还不够。无论如何,如果你有一个实际上是Rotation*Quaternion*,你仍然需要一个转换器来获得EulerAngles*

根据你的想法,你可以写:

// Here, I assumed that you will make the Rotation class pure virtual (I know that you didn't, but I think you should, and this only is an example)
enum class ROTATION_T {QUATERNION, EULERANGLES};
Rotation * convert(const Rotation * r)
{
// We assume here than the class Rotation has the attribute rotation_type saying the type.
if(r->rotation_type == ROTATION_T::QUATERNION)
{
// convert the Quaternion and return a EulerAngles*
}
else
{
// convert the EulerAngles and return a Quaternion*
}
}

我永远不会假装这是唯一的解决方案,但这是一种方法。

我希望它能/会帮助你。

您的基本问题是您尝试使用 OOP 解决某些问题,而绝对不需要 OO!面向对象的目标是:

  • 关注点的划分
  • 类层次结构的描述

这些要点都不适用于您的示例!(别担心,几乎每个从 OO 开始的人在第一次尝试时都会弄错这个......这会导致具有不必要依赖项的复杂代码。例如:

  • 为什么您的RigidBody存储轮换信息?它应该只是一个主体,旋转应该转换其基础数据。
  • 为什么你所有的类都有SomethingFromQuaternion功能?如果无法使用EulerAngles进行设置,则不应将其添加到名称中。另外,旋转与Quaternion耦合,它不应该作为杆件函数出现。

正确的方法

解决问题的正确方法是实现一个旋转类型和函数库来对这些类型进行操作。

//We need a forward declaration of Quaternion in order to get the conversion
//operator working.
struct Quaternion;
//All of the classes (probably) can be struct as they do not save any private
//data members.
struct EulerAngles
{
//members ...
//constructors...
//we declare a user defined conversion function
explicit operator Quaternion() const;
};
struct Quaternion
{
//members ...
//constructors...
//we declare a user defined conversion function
explicit operator EulerAngles() const;
};
struct RigidBody
{
//members ...
//constructors...
//we need a friend declaration to make our life easier
friend auto inplace_rotate(RigidBody&,const Quaternion&) -> void;
};
//We actually need to define the conversion after the class declaration,
//or the compiler complains...
Quaternion::operator EulerAngles() const
{
//add actual conversion here...
return EulerAngles{};
}
EulerAngles::operator Quaternion() const
{
//add actual conversion here...
return Quaternion{};
}
//the actual rotation function
auto inplace_rotate(RigidBody& body, const Quaternion& rotation) -> void
{
//implement...
//notice the friend declaration in the RigidBody class, which gives us direct access.
//(which we need in the case of private variables)
}
auto rotate(RigidBody body, const Quaternion& rotation) -> RigidBody
{
//using the body without a reference, gives us the copy we need.
//we are left with our rotation.
inplace_rotate(body, rotation);
return body;
}
int main()
{
auto body = RigidBody{};
//You need to get this from the user input.
auto euler_rotation = EulerAngles{};
//Using the user defined conversion operator.
auto quaternion = static_cast<Quaternion>(euler_rotation);
//Do the rotation.
inplace_rotate(body, quaternion);
return 0;
}

这种方法似乎与您编写的方法没有什么不同,但它大大减少了类之间的互连量。现在可以单独查看每个类,而无需考虑所有其他类。此外,它允许我们使用 c++ 方式和用户定义的转换器来定义转换。(https://en.cppreference.com/w/cpp/language/cast_operator)拉出旋转功能并使其独立,使我们能够轻松地将其扩展到一个地方(您无需更新您的身体类别即可对其进行更改)。

此外,您甚至可以为您的EulerAngles定义简单的便利重载:

auto inplace_rotate(RigidBody& body, const EulerAngles& rotation)
{
return inplace_rotate(body, static_cast<Quaternion>(rotation));
//technically returning here is unnecessary because the return type is void,
//but this allows you to consistenly change the inplace_function at any time
//without needing to update the convenience funtion
}
auto rotate(RigidBody body, const EulerAngles& rotation)
{
return inplace_rotate(body, static_cast<Quaternion>(rotation));
}

您的实际问题?

我想混淆是由于您试图让用户输入同时适用于QuaternionsEulerAngles。有几种方法可以解决此问题:

  1. 编写一个返回已旋转实体的用户函数。
  2. 使用std::variant(c++17) 或 boost 替代方案。
  3. 返回到类继承。

我建议您使用 1 或 2,但是如果无法做到这一点,您可以执行以下操作 (3):

#include <string>
#include <memory>
//An empty base class for rotation values.
struct Rotation {};
struct EulerAngles : Rotation
{
//...
};
struct Quaternion : Rotation
{
//...
};
auto parse_user_input(const std::string& input)
{
if (input == "euler")
return std::make_unique<Rotation>(EulerAngles{});
else if (input == "quaternion")
return std::make_unique<Rotation>(Quaternion{});
else
throw;
}