C++语义类型封装

6

我有一个数据类型,例如class Vector3。现在我需要创建几个具有与Vector3相同接口的类,但具有更高级别的语义(例如:PositionVelocity)。使用typedef不足够,因为我需要这些类型是不同的,以便它们可以用于重载。在C++0x中,我可能可以使用构造函数继承:

struct Position: public Vector3 {
    using Vector3::Vector3;
};

这样做会有什么问题吗?有更好的方法吗?是否可以不使用C++0x特性,而且不必显式编写所有Vector3构造函数来完成它?


问题在于位置隐式地可转换为向量(而且从技术上讲,为了安全使用,向量现在必须具有虚析构函数)。 - jalf
@jalf:即使派生类没有添加任何数据或方法,虚析构函数是否真的需要? - Juraj Blaho
jalf:我在我的答案中讨论了转换的话题。 - sehe
如果您通过指向基对象的指针删除派生对象并且基析构函数不是虚拟的,则无论派生类型是否添加任何内容,都将导致未定义的行为。 - David Rodríguez - dribeas
是的,根据标准来说需要这样做。实际上,如果没有虚拟析构函数通常也能正常工作,但你无法保证它一定可行(特别是编译器在优化时经常利用某些未定义行为,它们假设这种情况永远不会发生,所以如果确实发生了,可能会导致各种奇怪的行为)。 - jalf
2个回答

5
考虑使用标签结构。
struct tagPosition {};
struct tagDirection {};
struct tagGeneric {};

namespace detail
{
    template <typename Tag=tagGeneric>
        class Vector3
    {
        // business as usual
    };
}

typedef detail::Vector3<tagPosition>  Position;
typedef detail::Vector3<tagDirection> Direction;
typedef detail::Vector3<tagGeneric>   Vector3;

如果有转换运算符/构造函数,可以获得额外的加分:

    template <typename Tag=tagGeneric>
        class Vector3
    {
        template <typename OtherTag>
            explicit Vector3(const Vector3<OtherTag>& rhs) { /* ... */ }

//      template <typename OtherTag>
//            operator Vector3<OtherTag>() const { return /* ... */ }
    };

如果你喜欢冒险,可以省略 explicit 关键字,或者启用隐式转换运算符。这会带来一个“好处”,即可以启用像这样的“放荡”的运算符解析:
 Position pos;
 Direction dir;
 Generic gen;

 dir = gen + pos; // you see why I call it 'promiscuous'?

我建议(相反地)为这种情况定义显式的操作符(自由函数:)。
 Position operator+(const Position& v, const Translation& d) { /* .... */ }

那样你的类模型就能反映出类的语义。 C++0x可能包含使显式转换运算符成为可能的内容,如果我没记错的话:

在转换构造函数的情况下,您可以通过将构造函数声明为显式来禁用隐式转换。N1592提案将此关键字的语义扩展到所有转换运算符。声明为显式的转换运算符将不执行隐式转换。相反,程序员必须显式调用它


如果你想要数学上的正确性,你应该只允许以下几种情况:位置 + 向量 = 位置;位置 - 位置 = 向量;方向 == 向量;方向 * 速度 = 速度(速度是单位为1/时间的标量!)。位置不严格属于向量,而是仿射空间中的点。你的“速度*方向”很令人困惑——两者都是向量,它们的乘积意味着什么? - Kerrek SB
@Kerrek:我没有这样的假设,它们也不相关。我的示例中的语义不需要正确或不正确。 _(此外,你怎么能先说我的示例是错误的,然后才问我这些东西的含义呢?也许Direction根据定义就是一个Unity向量。请注意,示例没有指定Direction如何从Vector3派生。只需想象它在示例中具有适当的不变量即可)_。--只是说一下! - sehe
@sehe:你把“Velocity”变成了一个向量。现在“Velocity times Direction”可能意味着什么?只是好奇! - Kerrek SB
@Kerrek:是的,那里有脑损伤。编辑我只是盲目地从问题中复制了它 -- 结果证明 :) - sehe
@sehe:我可以看出如何构思一个专门的“Direction”类来表示单位向量(所有操作都归一化?)... 我想知道那是否有用。我猜它只需要实现两个标量数据成员(至少在方向上)。 - Kerrek SB
@Kerrek:你已经偏离主题了。我尝试在我的示例中减少混乱,供参考。祝好。 - sehe

4

当我需要相似但不同类型时,通常会使用一个虚拟模板参数,例如(未被编译器修改的临时解决方案)。

template< class UniqueId >
struct Blah {};

typedef Blah< struct AlphaId > Alpha;
typedef Blah< struct BetaId > Beta;

这可能是你所需要的,也可能不是。
祝好!

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