使用临时对象重载 -> 运算符

3

我正在为一个网格类编写包装器,试图实现更直观的接口。该网格使用相对简单的迭代器,可以进行增量和比较,但不能取消引用;相反,您必须从网格中获取它们关联的特征句柄。同样,特征句柄也是相对简单的,因此要从顶点获取位置/颜色/任何其他信息,您必须在网格上调用另一个函数:

Mesh mesh;
VertexIterator vertices = mesh.VerticesBegin();
VertexHandle vertex = mesh.VertexIteratorToHandle(vertices);
Vector3 vertexPosition = mesh.GetVertexPosition(vertex);

我希望能够做到以下操作:

取而代之,我想这样做:

MeshWrapper<Mesh> wrapper(mesh);
Vector3 vertexPosition = wrapper.VerticesBegin()->Position();

为了使这种更方便的风格成为可能,我为网格、它的迭代器和句柄提供了包装类:
template <class Mesh>
class SmartVertexHandle
{
public:
    SmartVertexHandle(Mesh::VertexHandle dumbVertexHandle, Mesh* parent);

    Vector3 Position();
    Vector3 Color(); 
    // etc ...
private:  
    Mesh* m_parent;

    typename Mesh::VertexHandle m_dumbVertexHandle;
}

template <class Mesh>
class SmartVertexIterator
{
public:
    SmartVertexHandle<Mesh>* operator->();
    // etc ...
private:
    Mesh* m_parent;
    typename Mesh::VertexIterator m_dumbVertexIterator;
}

实现 -> 运算符是我困扰的问题。我需要返回一个指向 SmartVertexHandle 的指针,但是从网格中获取的却是一个愚蠢的 Mesh::VertexHandle。我目前解决这个问题的方法如下:
template <class Mesh>
class SmartVertexIterator
{
public:
    SmartVertexHandle<Mesh>* operator->()
    {
        m_vertexWrapper = SmartVertexHandle<Mesh>(m_parent->VertexIteratorToHandle(m_dumbVertexIterator), m_parent);
        return &m_vertexWrapper;
    }
private:
    Mesh* m_parent;
    typename Mesh::VertexIterator m_dumbVertexIterator;

    SmartVertexHandle<Mesh> m_vertexWrapper;
}

这让我感到非常糟糕,充满危险,更不用说浪费空间了。有没有什么方法可以避免这种情况呢?抱歉问题有点长,谢谢 :)

如果你知道你将要封装哪个类(例如Mesh),那么为什么MeshWrapper需要带一个模板参数? - Maxpm
我有几个不同的Mesh类,具有相同的API(例如运行时/离线版本)。然而,模板化对于我遇到的问题并不是很相关,对吧? - Hexanol
在你的情况下,迭代器是什么?它支持哪些功能?它只是解引用(生成“SmartVertexHandle”)和增量吗? - Vlad
是的,就我所知,这与问题无关。 - Maxpm
愚蠢迭代器(如Mesh :: VertexIterator等)只执行前缀++--。智能迭代器(如SmartVertexIterator等)还应该执行后缀递增/递减(不是问题)并反引用到SmartVertexHandle - Hexanol
1个回答

4

自定义的operator->有一个特殊之处,它的行为就像在返回值上递归调用operator->一样。因此,对于给定的T some_type::operator->() const,其行为如下:

some_type()->some_member;
// moral equivalent:
some_type()::operator->()->some_member;

通常不会注意到这一点,因为返回的是一个普通指针,就像您正在尝试的那样,所以在第一个operator->之后,使用内置的->,因此链只有1层深度。这似乎可以满足您的需求,您可以使用以下方法:
SmartVertexHandle<Mesh> SmartVertexIterator<Mesh>::operator->();

并且

SmartVertexHandle<Mesh>* SmartVertexHandle<Mesh>::operator->()
{ return this; }

当用户执行wrapper.VerticesBegin()->Position()时,VerticesBegin()会返回一个SmartVertexIterator,第一个operator->返回一个SmartVertexHandle,而第二个隐式的operator->返回一个指向该临时句柄的指针,在此内置的->调用SmartVertexHandle::Position。可以想象这个智能句柄被构造和设计成知道如何执行parent->GetVertexPosition(parent->GetVertexHandle( ... ) )。当完整表达式被评估时,临时SmartVertexHandle会优雅地消失。
请注意,我重用了您为SmartVertexHandleSmartVertexIterator使用的名称,但我不知道您的类是否可以(重新)设计成像这样使用。在理想情况下,我不一定会为用户设计单独的SmartVertexHandle;我可能会编写一个SmartVertexIterator::proxy类型,让operator->返回它,然后使用上述技术。
总之,我认为您当前的方法是急切版本,即每当构造迭代器时计算并存储句柄,并在迭代器增加时重新计算。我的方法是更懒的版本,只有在需要时才构建句柄(但不存储它,并且即使迭代器相同,也不会每次重新构建它)。我不认为第一个方法是“非常糟糕”的,因为我不知道构建句柄与迭代器相比的成本高昂程度,或者处理每个迭代器步骤时句柄被解引用/使用的频率如何。第三种方法甚至可以更慢。
在这两种情况下,我建议不向用户公开SmartVertexHandle(当然,也许您有要求)。

1
非常感谢,这是一个很好的答案!在我的情况下,我相信需要一个单独的SmartVertexHandle,因为我还在其上进行了模板化的SmartVertexIterator(为简洁起见,在问题代码中省略了该部分)。虽然智能迭代器对于所有可能的特征有效负载都是有效的,但我需要几个不同的智能句柄来处理不同的网格类型。 - Hexanol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接