使用静态函数初始化静态 const std::array 会删除 const(使其可写)

Initializing static const std::array with a static function removes const (makes it writable)

提问人:G Stepanovic 提问时间:4/22/2023 最后编辑:G Stepanovic 更新时间:4/23/2023 访问量:165

问:

我有一个包含私有成员的类(结构)。我希望这个成员是静态和恒定的(不可写的)。看起来好像通过静态函数添加初始化,破坏了成员数组的恒定性。 我本来希望编译器在我尝试写入定义为 const 的数组时会抱怨。相反,当我运行时,我得到:static const std::array

  1. 使用列表初始值设定项时的 SEGFAULT,
  2. 使用上述功能时能够写入

类还通过返回 std::array 迭代器来提供迭代器。当我通过初始化器列表(在结构声明之外)初始化 std::array 时,然后尝试通过迭代器修改数组中的元素时,我得到了 SEGFAULT,尽管编译器没有抱怨(有点意料之中且很好)。 但是,当我通过另一个函数初始化数组时(在下面的代码中,我能够更改元素值,这是不行的。 值得一提的是,下面的代码只是为了重现这个问题。我真的需要以一种非平凡的方式初始化更大大小的静态常量数组的能力 - 即使用一些三角函数。 我不明白为什么会这样。任何帮助或指导都表示赞赏。谢谢。static std::array<int, 4> HalfCircleStatic_init();

我试过这个:

#include <iostream>
#include <array>

struct ArrayContainer
{
    using iterator = typename std::array<int, 4>::iterator ;

    inline constexpr iterator begin() { return iterator(&arr[0]); }
    inline constexpr iterator end()   { return iterator(&arr[0] + arr.size()); }

private:
    static const std::array<int, 4> arr;

    static std::array<int, 4> HalfCircleStatic_init();
};

const std::array<int, 4> ArrayContainer::arr = {1,2,3,4};

std::array<int, 4> ArrayContainer::HalfCircleStatic_init() {
    std::array<int, 4> retVal{};
    for (int i = 0; i < 4; ++i) retVal[i] = i+1;
    return retVal;
}

int main() {
    ArrayContainer arrCont;
    auto it = arrCont.begin();
    std::cout << "Value at 0: " << *it << std::endl;
    *it = 5;
    std::cout << "Value at 0: " << *it << std::endl;
    return 0;
}

生产:

Value at 0: 1

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

如果我将初始值设定项(定义)更改为:

const std::array<int, 4> ArrayContainer::arr = ArrayContainer::HalfCircleStatic_init();

我明白了:

Value at 0: 1
Value at 0: 5

Process finished with exit code 0

在 Ubuntu 上使用 GCC 8.4

C++ gcc stdarray 静态初始化

评论

1赞 Evg 4/22/2023
UB 可以表现为工作代码。这并不意味着代码实际上是正确的。
0赞 G Stepanovic 4/23/2023
我知道我可能在某处调用了 UB,但这里的 UB 到底是什么?
0赞 Evg 4/23/2023
但这里的 UB 到底是什么 - 尝试修改常量变量。你取一个指向 const () 的指针,抛弃 constness (),然后使用生成的指针写入 const 变量 ()。尝试替换为 .&arr[0]iterator(&arr[0])*it = 5iterator(&arr[0])iterator{&arr[0]}

答:

0赞 o_oTurtle 4/22/2023 #1

你可以这样编码:

{
    // In your class
    static constexpr std::array<int, 4> HalfCircleStatic_init();
}

constexpr std::array<int, 4> ArrayContainer::HalfCircleStatic_init() {
    std::array<int, 4> retVal{};
    for (int i = 0; i < 4; ++i) retVal[i] = i+1;
    return retVal;
}

触发 SEGFAULT 的原因是您正在写入程序的只读会话,并且只读属性是在运行时之前确定的。通过直接提供数字,它们通常由程序中的编译器硬编码,即您的二进制程序直接包含 ,当程序加载时,它们将作系统放在只读页面上;但是通过使用函数,在没有优化的情况下,初始化实际上是一个运行时的事情,所以编译器必须把你的数组放到一个可写的会话中(否则初始化本身会触发SEGFAULT,对吧?上面的代码所做的只是将运行时的东西移动到编译时间(by ),以便编译器尝试计算它,然后可以在二进制程序中对其进行硬编码。如果你想强制编译器这样做,并在失败时报告错误,可以这样做。1, 2, 3, 4constconstexprconsteval

但是,对于浮点数组,编译器无法在编译时计算它们。保留是通过编译器保护只读数据的最可靠方法,您不应依赖 SEGFAULT 进行调试。例如,您可以返回类似 的东西,以在运行之前而不是运行后终止写入行为。constcbegin()

评论

0赞 G Stepanovic 4/23/2023
使用 consexpr 将是完美的,但实际上我需要使用 cmath 函数启动数组,但这行不通。
1赞 user2846855 4/22/2023 #2
> inline constexpr iterator begin() { return iterator(&arr[0]); } inline
> constexpr iterator end()   { return iterator(&arr[0] + arr.size()); }

上面可能是一个指标,应该可以返回 arr.begin() 和 arr.end()

尝试const_iterator而不是迭代器 (或者将数组成员设置为非常量 -- 如果您希望在构造后能够修改它)

评论

0赞 Red.Wave 4/22/2023
甚至和可用。黄金法则是不要在家尝试指针算术。为了安全起见,我会从值派生类型:begin(arr)end(arr)using iterator = decltype(begin(arr));
0赞 user2846855 4/22/2023
我不是那些认为这有意义的人之一。首先,const_iterator 和迭代器在概念上是截然不同的东西,拥有 const std::array 和非 const std::array 非常重要,并且对整体设计有影响。隐藏它可能很方便,因为你现在可以在不改变任何其他内容的情况下改变 arr 的恒常性---这是一个危险的错误结论来得出 IMO(我可能误读了第一个词,你建议另一种方法——仍然不同意,尽管在上下文中)
0赞 G Stepanovic 4/23/2023
我实际上想要 oposite,我希望成员数组是不可写的。当我使用init list时,一切都很好。当我使用函数启动它时,我能够写入数组,这不应该是这种情况,因为它是静态常量
0赞 user2846855 4/23/2023
返回类型(迭代器)允许写入: Use: using const_iterator = typename std::array<int, 4>::const_iterator ;内联 constexpr const_iterator begin() { return arr.begin(); } 内联 constexpr const_iterator end() { return arr.end();至于为什么你的原始代码会编译,我不知道,(UB已经评论过)
0赞 G Stepanovic 4/23/2023 #3

好的,似乎这可能有效(在某种程度上)。使用 lambda 初始化成员数组:static const

const std::array<int, 4> ArrayContainer::arr = {
        [] {
            std::array<int, 4> retVal{};
            for (int i = 0; i < 4; ++i) retVal[i] = i + 1;
            return retVal;

        }() };

这是一致的,因为当我尝试写作时,它会产生段错误。

评论

1赞 o_oTurtle 4/23/2023
我已经说过,编译器无法在编译时计算浮点,如果你调用一些浮点函数,几乎不可能将其存储在只读会话中。请参阅此处的反例,我像您一样编写代码,仍然可以在不触发 SEGFAULT 的情况下成功写入数组。使用 lambda 表达式是我答案的第一个版本,但我稍后会编辑它,因为如果它是一个浮点数组,你仍然可以编写它。
0赞 o_oTurtle 4/23/2023
你是一个新来的人,所以我尽力表现得很友善,但反驳需要彻底的证据,在计算机科学中做所有可能的测试或实验是很重要的。“整数是对的”绝不意味着“浮点是对的”。
0赞 o_oTurtle 4/23/2023
整数是正确的原因是编译器的优化,然后在编译时计算它们,并在只读会话中仍然硬编码。请参阅此处的程序集,其中编码为 ,等。这就是为什么我在回答中说“没有优化”;但是,不可能像编译器优化那样合理地预测,正如您所看到的,Lambda 可能可以,而成员函数不能,因此这样做并不安全。arr.long 1