我正在设计一个API,并正在考虑使用“不可变”结构”或“只读结构”。使用一个简化的示例,它可能类似于:
struct vector {
const float x;
const float y;
};
struct vector getCurrentPosition(void);
struct vector getCurrentVelocity(void);
只要我在堆栈上返回不变结构,一切都可以正常工作。但是,在实现以下功能时遇到问题:
void getCurrentPositionAndVelocity(
struct vector *position,
struct vector *velocity);
在这一点上,我不想引入一个包含两个向量的新的不可变结构。我也无法做到这一点:
void
getCurrentPositionAndVelocity(
struct vector *position,
struct vector *velocity)
{
*position = getCurrentPosition();
*velocity = getCurrentVelocity();
}
因为position
和velocity
是只读的(尽管我安装的clang
incorrectly compiles this without warning版本)。
我可以使用memcpy
来解决此问题,如下所示:
void
getCurrentPositionAndVelocity(
struct vector *position,
struct vector *velocity)
{
struct vector p = getCurrentPosition();
struct vector v = getCurrentVelocity();
memcpy(position, &p, sizeof *position);
memcpy(velocity, &v, sizeof *velocity);
}
这看起来很糟糕,但我相信它不会误导API用户,vector
结构看起来仍然是不变的。为此,我可以另外添加一个必需的初始化程序,在该初始化程序中,仅对具有特殊值的vector
结构成功调用此类函数。像这样:
const struct vector * const VECTOR_UNINITIALIZED;
用户应该去的地方
struct vector position = *VECTOR_UNINITIALIZED;
struct vector velocity = *VECTOR_UNINITIALIZED;
在调用getCurrentPositionAndVelocity()
之前。在memcpy
插入之前,该实现将使用memcmp
断言向量确实具有“未初始化的哨兵”值。 (为此,我意识到只有在某些确实未使用的值可以充当“未初始化的标记”值的情况下,此方法才有效,但我认为情况就是如此。)
我的问题是,从API用户的角度来看,const
关键字的用法是否符合其意图?从编译器的角度来看,是否存在任何风险,因为严格来讲,该代码可能违反了const
关键字所指示的只读语义?作为API用户,您对这种方法有什么看法?或者您有什么更好的建议?
答案 0 :(得分:3)
TL; DR :
const关键字的这种用法是否符合其意图?
否。
我的问题是
const
关键字的用法是否符合 从API用户的角度来看,其意图是什么?
我不确定API用户的观点。实际上,您提出的设计似乎可能比平时产生更大的用户视角多样性,因为明显的预期行为与C语言要求不一致。
会不会有任何 从编译器的角度来看存在风险,因为该代码可能会 严格来讲,违反了只读语义,如 const关键字?
是的。具体来说,
如果试图修改用 通过使用带有非const限定的左值的const限定类型 类型,行为是不确定的。
发生UB时,编译器可能会做各种有趣的事情。更有可能的是,他们可能会假设某些未定义的行为不会发生。从而,
例如,当编译一个调用您的getCurrentPositionAndVelocity()
函数的程序时,编译器可能会假定该函数未修改提供给它的结构的const
成员。否则尝试修改它们实际上可能会失败。或其他任何东西,实际上是“未定义”的意思。
作为API用户,您对此有何看法 方法还是您有什么更好的建议?
我认为我的API提供者应该尽力提供符合语言标准的实现。
此外,我想知道您认为谁通过使结构成员成为const
来保护我免受保护,为什么您认为您比我更了解是否应该修改结构成员。我也想知道,为什么有人会认为这种保护足够重要,足以保证参加const
结构成员的所有很多问题。
关于更好的建议,只是不让成员成为const
会怎么样?它将为您和您的用户节省痛苦。
关于修改,其中添加了对How to initialize const members of structs on the heap的引用,就标准而言,此处讨论的案例与此处考虑的案例有很大不同。动态分配的内存没有声明的类型,但根据已写入的内容和/或访问方式,可能具有有效类型。标准的paragraph 6.5/6中介绍了有关规则,您可以在SO上其他位置的几个答案中找到有关此问题的讨论,例如您在评论中链接的this one。
最重要的是,分配了生存期的对象会从写入的第一个数据中获取其有效类型,并且该首次写入可被视为其有效的初始化(尽管在标准中未使用后者)。随后的操作必须遵守有效类型,包括根据类型paragraph 6.5/7递归地称为{严格别名规则”的约束,包括由于类型本身或其成员的const
性引起的约束。
具有静态或自动生存期的对象将其声明的类型作为其有效类型,并具有其初始值设定项(如果有)指定的初始值。它们也必须以与其有效类型一致的方式进行操作。
答案 1 :(得分:-1)
memcpy(position, &p, sizeof *position);
memcpy(velocity, &v, sizeof *velocity);
您滥用了与编译器的合同。您声明了const
,然后尝试解决。 极端不好的做法
只要您不想违反此协议,就不要将结构成员声明为const
。