这个C++setter/getter模式破坏了什么
What does this C++ setter/getter pattern break?
在C中使用GLSL语法++
我编写了自定义矢量类,如vec2
、vec3
等,它们模仿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的语法创建向量并访问它们的组件,并对它们执行操作,就好像它们是数字类型一样。并集允许我将第一个坐标无差别地称为x
或r
,就像GLSL中的情况一样。例如:
vec3 point = vec3(1.f, 2.f, 3.f);
vec3 other = point + point;
point.x = other.b;
喝水的问题
但GLSL也允许快速访问,即使组件之间有孔。例如,p.yx
的行为类似于vec2
,p
的x
和y
被交换。当没有组件重复时,它也是一个左值。一些例子:
other = point.xyy; /* Note: xyy, not xyz */
other.xz = point.xz;
point.xy = other.xx + vec2(1.0f, 2.0f);
现在,这可以使用标准的getter和setter(如vec2 xy()
和void xy(vec2 val)
)来完成。这就是GLM库的作用。
透明getter和setter
然而,我设计了这个模式,让我可以在C++中做完全相同的事情。由于所有东西都是POD结构,我可以添加更多的联合:
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
,但可以隐式转换为vec2
(因为有vec2
构造函数允许它),并且可以被分配vec2
(因为它的赋值运算符重载)。
我对结果很满意。上面的GLSL代码有效,我相信我得到了不错的类型安全性。我可以在C++代码中#include
一个GLSL着色器。
限制
当然也有局限性。我知道以下几种:
- CCD_ 19将是CCD_ 20而不是预期的CCD_。这是故意的,我不知道这是否会有问题
&foo.xz
不能用作vec2*
。这应该没问题,因为我只按值传递这些对象
所以我的问题是:我可能忽略了什么,这会让我的生活在这种模式下变得困难此外,我在其他地方还没有发现这种模式,所以如果有人知道它的名字,我很感兴趣。
注意:我希望使用C++98,但我确实依赖于允许通过并集进行类型双关的编译器。我不想要C++11的原因是我的几个目标平台上缺乏编译器支持;不过,我感兴趣的所有编译器都支持类型punning。
简而言之:我认为很难确保这种模式有效——这就是你为什么要问的原因。此外,这个模式可以被一个标准的代理模式所取代,这样更容易保证正确性。不过,我不得不承认,当静态创建代理时,基于代理的解决方案的存储开销是一个问题。
上述代码的正确性
这是没有明显错误的代码;但转述一下C.A.R.Hoare,这不是一个明显没有bug的代码。此外,要说服自己没有窃听器有多难?我看不出该模式不起作用的任何原因,但要证明(即使是非正式的)它会起作用并不那么容易。事实上,尝试做一个证明可能会失败,并指出一些问题。为了安全起见,我将禁用MagicVecN
类的所有隐式生成的构造函数/赋值运算符,只是为了避免考虑所有相关的复杂性(见下面的小节);然而,这样做是被禁止的,因为对于工会成员来说,不能覆盖隐式定义的副本分配运算符,正如我的标准草案和GCC的错误消息所解释的那样:
member ‘MagicVec2<0, 0> vec3::<anonymous union>::xx’ with copy assignment operator not allowed in union
在所附的要点中,为了安全起见,我提供了一个手动实现。
请注意,MagicVec2的赋值运算符应该通过const引用来接受其参数(请参阅下面的示例,该示例适用于此);隐式转换仍然会发生(const引用将指向创建的临时;如果没有const限定符,这将不起作用)。
几乎有问题,但不完全
我以为a发现了一个bug(我没有发现),但考虑一下仍然有点有趣——只是看看必须覆盖多少案例才能排除潜在的bug。p.xz = p.zx
会产生正确的结果吗?我认为MagicVec2
的隐式赋值运算符会被调用,导致不正确的结果;事实上,它不是(我相信),因为I
和J
是不同的,是类型的一部分。当类型相同时怎么办?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
的复制构造函数。
好的,我已经发现了一个问题,尽管不是直接使用上面的代码。如果vec3
以某种方式成为一个模板类以支持例如int
加上float
,+
运算符变为:
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>
到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>
是合法的,并将返回vec3<float>
。
- #定义c-预处理器常量..我做错了什么
- 努力将整数转换为链表。不知道我在这里做错了什么
- 当我尝试添加 2 个大字符串时,我无法弄清楚出了什么问题
- .h 和.cpp文件分离时出错,但仅使用 .h 文件时没有错误.我做错了什么?
- 当我们为(;;) 写作时,它做了什么?for 循环中的双分号有什么作用?
- 我的C++线程做错了什么?
- 此测试()中发生了什么意外过程?为什么总是覆盖 ch[0 1 2..]?
- "delete"在 C++ 中实际上做了什么?
- 这C++代码中发生了什么C++(指数函数)
- 谁能告诉我我用 getline 做错了什么 (cpp) 格式
- C++std::atomic在程序员级别保证了什么
- 没有输出的合并排序我做错了什么?
- 我正在尝试使用 while 循环从字符串中删除字母,直到没有字母。我在这里做错了什么?
- 在C++中使用 AKS 素数测试计算双胞胎素数 我做错了什么?
- 任何人都可以告诉我我的 C++ 代码出了什么问题?
- 哪种方式更快?究竟发生了什么,我们没有看到什么?
- 我一直试图弄清楚我在这个链表程序中做错了什么
- 我正在尝试学习如何在 c++ 中传递指针,但出现错误:没有用于调用"test"的匹配函数。我做错了什么?
- 计时器坏了或者其他什么的
- SFML sf::Text::setFillColor 坏了,还是我做错了什么