代码之家  ›  专栏  ›  技术社区  ›  Reinier Torenbeek

使用const关键字是否符合其意图?

  •  1
  • Reinier Torenbeek  · 技术社区  · 6 年前

    我正在设计一个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 结构看起来仍然是不变的。为此,我还可以添加一个必需的初始值设定项,其中对这样的函数的调用只会在 矢量 具有特殊值的结构。类似:

    const struct vector * const VECTOR_UNINITIALIZED;
    

    用户应该做什么

    struct vector position = *VECTOR_UNINITIALIZED;
    struct vector velocity = *VECTOR_UNINITIALIZED;
    

    调用前 getCurrentPositionAndVelocity() . 以前 曼皮西 -ing,该实现将断言 memcmp 向量确实具有“未初始化的sentinel”值。(键入这个,我意识到只有在某些真正未使用的值可以充当“未初始化的sentinel”值时,这才有效,但我认为这是我的情况。)

    我的问题是 const 从API用户的角度来看,关键字是否符合其意图?从编译器的角度来看,是否存在任何风险,因为严格来说,此代码可能违反用 康斯特 关键字?作为一个API用户,您对这种方法有什么看法,或者您有什么更好的建议?

    2 回复  |  直到 6 年前
        1
  •  3
  •   John Bollinger    6 年前

    DR :

    使用const关键字是否符合其意图?

    不。


    我的问题是 const 关键字与 从API用户的角度来看,它的意图是什么?

    我不确定API用户的观点是什么。事实上,像您所提议的这样的设计可能会产生比通常更大的用户视角多样性,因为明显的预期行为与C语言要求不一致。

    有没有 从编译器的角度来看,这段代码可能 严格来说,违反了只读语义 const关键字?

    对。明确地,


    如果试图修改用 通过使用带有非常量限定符的左值来构造限定类型 类型,行为未定义。

    ( C2011, 6.7.3/6 )


    当ub发生时,编译器可能会做各种有趣的事情。其中更有可能的是,他们可能会假设一些未定义的行为 发生。因此, 例如,在编译调用 getCurrentPositionAndVelocity() 函数,编译器可能会假定函数不修改 康斯特 提供给它的结构的成员。或者修改它们的尝试实际上可能会失败。或者其他的,真的——这就是“未定义”的意思。

    作为一个API用户,您会如何看待这个问题? 接近还是你有更好的建议?

    我认为我的API提供者应该尽最大努力提供符合语言标准的实现。

    另外,我想知道你认为是谁通过让结构成员来保护我 康斯特 ,以及为什么您认为您比我更了解是否应该修改结构成员。我还想知道,为什么有人会认为这样的保护措施非常重要,足以保证 许多的 参加的问题 康斯特 结构成员。

    至于更好的建议,不让会员加入怎么样 康斯特 ?它将为您和您的用户节省悲伤。


    关于编辑 添加对的引用 How to initialize const members of structs on the heap 在标准方面,讨论的案例与这里考虑的案例有重要的不同。动态分配的内存没有声明的类型,但可能具有 有效的 根据写入的内容和/或访问方式键入。相关规则见 paragraph 6.5/6 你可以在其他地方的几个答案中找到关于这个标准的讨论,比如 this one 你在评论中链接的。

    底线是,具有分配生存期的对象从写入它的第一个数据中获取其有效类型,并且第一个写入可以被视为其有效初始化(尽管标准中没有使用后一个术语)。后续操作必须遵守有效类型,包括由 康斯特 类型本身或其成员的递归性,根据 paragraph 6.5/7 ,俗称“严格混叠规则”。

    具有静态或自动生存期的对象将其声明的类型作为其有效类型,并具有由初始值设定项(如果有)指定的初始值。它们也必须以与有效类型一致的方式进行操作。

        2
  •  -1
  •   0___________    6 年前
    memcpy(position, &p, sizeof *position);
    memcpy(velocity, &v, sizeof *velocity);
    

    你滥用了与编译器的合同。你申报了什么 const 然后你试着去解决它。 极坏的做法

    不要将结构成员声明为 康斯特 如果你想违反这个协议。