这个C ++ setter / getter模式打破了什么?

时间:2012-01-26 01:05:34

标签: c++ design-patterns glsl getter-setter

在C ++中使用GLSL语法

我编写了自定义矢量类,例如vec2vec3等模仿GLSL类型,看起来大致如下:

struct vec3
{
    inline vec3(float x, float y, float z)
      : x(x), y(y), z(z) {}
    union { float x, r, s; };
    union { float y, g, t; };
    union { float z, b, p; };
};

矢量的操作以这种方式实现:

inline vec3 operator +(vec3 a, vec3 b)
{
    return vec3(a.x + b.x, a.y + b.y, a.z + b.z);
}

这允许我使用类似GLSL的语法创建向量并访问它们的组件,并对它们执行操作,就像它们是数字类型一样。工会允许我将第一个坐标无差别地称为xr,就像GLSL中的情况一样。例如:

vec3 point = vec3(1.f, 2.f, 3.f);
vec3 other = point + point;
point.x = other.b;

流浪的问题

但GLSL也允许调整访问,即使组件之间存在漏洞。例如,p.yx的行为类似于vec2 p的{​​{1}}和x交换。当没有重复组件时,它也是左值。一些例子:

y

现在可以使用标准的getter和setter来完成,例如other = point.xyy; /* Note: xyy, not xyz */ other.xz = point.xz; point.xy = other.xx + vec2(1.0f, 2.0f); vec2 xy()。这就是GLM library的作用。

透明吸气剂和固定剂

然而,我设计了这种模式,让我在C ++中完全相同。由于一切都是POD结构,我可以添加更多联合:

void xy(vec2 val)

例如。 template<int I, int J> struct MagicVec2 { friend struct vec2; inline vec2 operator =(vec2 that); private: float ptr[1 + (I > J ? I : J)]; }; template<int I, int J> inline vec2 MagicVec2<I, J>::operator =(vec2 that) { ptr[I] = that.x; ptr[J] = that.y; return *this; } 类变为(我简化了一些事情,例如没有什么能阻止vec3在这里被用作左值):

xx

基本上:我使用一个联合来将向量的浮点组件与一个魔法对象混合,该对象实际上不是struct vec3 { inline vec3(float x, float y, float z) : x(x), y(y), z(z) {} template<int I, int J, int K> inline vec3(MagicVec3<I, J, K> const &v) : x(v.ptr[I]), y(v.ptr[J]), z(v.ptr[K]) {} union { struct { float x, y, z; }; struct { float r, g, b; }; struct { float s, t, p; }; MagicVec2<0,0> xx, rr, ss; MagicVec2<0,1> xy, rg, st; MagicVec2<0,2> xz, rb, sp; MagicVec2<1,0> yx, gr, ts; MagicVec2<1,1> yy, gg, tt; MagicVec2<1,2> yz, gb, tp; MagicVec2<2,0> zx, br, ps; MagicVec2<2,1> zy, bg, pt; MagicVec2<2,2> zz, bb, pp; /* Also MagicVec3 and MagicVec4, of course */ }; }; ,但可以隐式地转换为vec2(因为有{{1}允许它的构造函数),并且可以分配vec2(因为它重载的赋值运算符)。

我对结果非常满意。上面的GLSL代码有效,我相信我得到了不错的类型安全性。我可以在我的C ++代码vec2中使用GLSL着色器。

限制

当然有一些限制。我知道以下几点:

  • vec2将是#include而不是预期的sizeof(point.xz)。这是设计上的,我不知道这是否有问题。
  • 3*sizeof(float)不能用作2*sizeof(float)。这应该没问题,因为我只是按值传递这些对象。

所以我的问题是:我可能忽略了什么会让这种模式让我的生活变得困难?另外,我还没有在其他任何地方找到这种模式,所以如果有人知道它的名字我就是感兴趣。

注意:我希望坚持使用C ++ 98,但我依靠编译器允许通过联合进行类型惩罚。我不想要C ++ 11的原因是我的几个目标平台缺乏编译器支持;但是,我感兴趣的所有编译器都支持打字。

2 个答案:

答案 0 :(得分:3)

好的,我已经发现了一个问题,但不是直接使用上面的代码。如果vec3以某种方式成为模板类,以便除了int之外还支持例如。 float,并且+运算符变为:< / p>

template<typename T>
inline vec3<T> operator +(vec3<T> a, vec3<T> b)
{
    return vec3<T>(a.x + b.x, a.y + b.y, a.z + b.z);
}

然后这段代码不起作用:

vec3<float> a, b, c;
...
c = a.xyz + b;

原因是找出+的参数需要模板参数推导(T = float隐式转换(从MagicVec3<T,0,1,2>到{{} 1}},这是不允许的。

但是我可以接受一个解决方案:编写所有可能的显式运算符。

vec3<T>

这也可以让我为隐式促销定义规则,例如我可以决定inline vec3<int> operator +(vec3<int> a, vec3<int> b) { return vec3<int>(a.x + b.x, a.y + b.y, a.z + b.z); } inline vec3<float> operator +(vec3<float> a, vec3<float> b) { return vec3<float>(a.x + b.x, a.y + b.y, a.z + b.z); } 是合法的并且会返回vec3<float> + vec3<int>

答案 1 :(得分:3)

简而言之:我认为很难确保这种模式有效 - 这就是你要问的原因。此外,这种模式可以用标准代理模式代替,为此更容易保证正确性。我不得不承认,当代理是静态创建时,基于代理的解决方案的存储开销是一个问题。

上述代码的正确性

这是没有明显错误的代码;但是释义C. A. R. Hoare,这不是明显没有错误的代码。而且,说服自己没有错误有多难? 我没有看到任何理由为什么这种模式不起作用 - 但要证明(甚至是非正式的)它将起作用并不容易。事实上,尝试做证明可能会失败并指出一些问题。 为了安全起见,我将禁用MagicVecN类的所有隐式生成的构造函数/赋值运算符,以避免考虑所有相关的复杂性(参见下面的小节);但是这样做是被禁止的,因为对于工会成员,我不能覆盖隐式定义的复制赋值运算符,正如我所拥有的标准草案和GCC的错误消息所解释的那样:

member ‘MagicVec2<0, 0> vec3::<anonymous union>::xx’ with copy assignment operator not allowed in union

在附加的要点中,我提供手动实现以确保安全。

请注意,MagicVec2的赋值运算符应该通过 const 引用接受其参数(参见下面的示例,其中有效);隐式转换仍然会发生(const引用将指向创建的临时转换;如果没有const限定符,这将无效。)

几乎是问题,但不完全

我认为发现了一个错误(我没有发现),但是仍然有点考虑 - 只是为了看看必须覆盖多少个案例以排除潜在的错误。 p.xz = p.zx会产生正确的结果吗?我认为MagicVec2的隐式赋值运算符会被调用,导致错误的结果;事实上,它不是(我相信)因为IJ不同而且是类型的一部分。什么时候类型相同? p.xx = q.rr是安全的,但是p.xx = p.rr很棘手(即使它可能是愚蠢的,但它仍然不会破坏内存):是隐式生成的赋值运算符memcpy吗?答案似乎是否定的,但如果是,那么在重叠的内存间隔之间这将是memcpy,这是未定义的行为。

更新:实际问题

正如OP所注意到的,还为表达式p.xz = q.xz调用了默认的复制赋值运算符。在这种情况下,它实际上也会复制.y成员。如上所述,对于属于联合的数据类型,不能禁用或修改复制赋值运算符。

代理模式

此外,我认为有一个更简单的解决方案,即代理模式(您正在部分使用)。 MagicVecX应该包含指向包含类而不是ptr的指针;这样你就不需要使用工会了。

template<int I, int J> struct MagicVec2
{
    friend struct vec2;
    inline MagicVec2(vec2* _this): ptr(_this) {}
    inline vec2 operator=(const vec2& that);
private:
    float *ptr;
};

我通过编译(但不链接)此代码对此进行了测试,该代码草拟了建议的解决方案:https://gist.github.com/1775054。请注意,代码不完整也未经过测试 - 还应覆盖MagicVecX

的复制构造函数