保护 std::array 中的单个值,同时允许完全覆盖

Protect individual values in std::array while allowing complete overwrite

提问人:fisherwebdev 提问时间:9/29/2023 最后编辑:fisherwebdev 更新时间:9/29/2023 访问量:84

问:

我有一个全局状态的数组。这是在嵌入式/微控制器环境中运行的,而不是我可能更关心全局状态的大型应用程序。

如何声明数组,使其成员无法更改,但仍可更新副本,并在需要时仍完全覆盖全局数组?

我有兴趣使用一个不可变数组,以便能够快速检查它在从突变函数返回时是否发生了变化。也就是说,如果内存地址发生了变化,那么我知道它已被复制和更新。

如果它已更新,那么我想用更新的版本覆盖原始全局数组。

但是,我想保护数组的各个值不会在 main() 中被意外覆盖,或者以其他方式访问和直接更新全局数组。也许我是偏执狂,但为了这个问题,让我们说这是一个合理的担忧。

唯一可能的更新应该通过复制/地址更新来完成。

此版本有效,但可以访问和直接更新全局数组:

#include <array>
#include <iostream>

std::array<int, 3> arr = {1, 2, 3}; // global array

std::array<int, 3> const * mutate(std::array<int, 3> const * ptr) {
  // (*ptr)[2] = 9; // I like how this is prevented by const
  // return ptr; // uncomment to test no mutation

  // mutation via copy, retain new array on the heap
  std::array<int, 3> * ret = new std::array<int, 3>;
  std::copy((*ptr).begin(), (*ptr).end(), (*ret).begin());
  (*ret)[0] = 9;
  return ret;
}

int main() {
  std::array<int, 3> const * retPtr = mutate(&arr);

  if (retPtr == &arr) {
    std::cout << "pointers are the same\n";
  } else {
    std::cout << "pointers are different\n";
    arr = (*retPtr);
    // do expensive things here with the new state
  }
  delete[] retPtr;

  for (int val : arr) {
    std::cout << val;
    std::cout << "\n";
  }

  return 0;
}

首先,我尝试将数组声明为 .这使我无法完全覆盖数组。std::array<int, 3> const

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

...

arr = (*retPtr); // Compilation error: no viable overloaded '='

然后我尝试将其声明为 ,但这阻止了我无法在突变函数中更改副本。std::array<int const, 3>

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

std::array<int const, 3> * mutate(std::array<int const, 3> * ptr) {
  std::array<int const, 3> * ret = new std::array<int const, 3> {0,0,0};
  std::copy((*ptr).begin(), (*ptr).end(), (*ret).begin());

  // Compilation error: cannot assign to return value because function 'operator[]' 
  // returns a const value
  (*ret)[0] = 9; 

  return ret;
}

提前感谢您提供的任何帮助!

C++ 不可变性 stdarray

评论

8赞 paddy 9/29/2023
这里真正的答案是不要将数组本身公开为全局数组。将其封装在你自己的类中,该类公开了你需要的基本功能(例如索引、通过指针访问)。这些方法不允许修改基础数据,因此不可能意外覆盖数据(在有效程序中)。然后,将执行“替换”逻辑的方法添加到该类中,或者在必要时显式请求可修改的数组引用。您可以定义类的全局实例,并像使用数组一样使用它。这种封装是面包和黄油 C++。
0赞 Remy Lebeau 9/29/2023
"我怎样才能声明数组,使其成员不能被改变,...但在需要时仍然完全覆盖全局数组?-你不能。整个数组是只读的,或者整个数组是可写的。没有中间地带。您需要将数组隐藏在抽象后面,以实现所需的控件。
0赞 tadman 9/29/2023
您使用 和 自由分配 。值得注意的是,你使用的指针越多,你的 C++ 体验就越悲惨。这里是绝对必要的吗?newdelete[]
0赞 tadman 9/29/2023
您还有一个特殊的习惯,即将指针类型的指示器间隔开来,这使它看起来像乘法。它通常坚持使用类型或变量之一,这两种样式都有很好的案例,但让它“浮动”会分散注意力。这是或但不是它看起来像操作员的地方。*const char* xconst char *xconst char * x
0赞 fisherwebdev 10/2/2023
@paddy - 我想知道从您描述的类初始化新对象的过程与替换数组的过程有多昂贵。也许有更好或更坏的方法可以做到这一点?从根本上说,我希望能够比较新旧状态对象(数组或其他)的内存地址,以查看是否有任何状态发生了变化,然后只在必要时进行代价高昂的副作用,最好是在不同的线程上。这样做的目的是在 main() 中以非常快的速度运行一个 while(true) 循环,使微控制器能够响应 MHz 范围内频率的信号。

答:

2赞 doug 9/29/2023 #1

假设您使用的是 c++20 或更高版本,这现在是可行的。基本生活的改变使得更换对象成为可能,只要被替换的对象可以透明地替换。这限制了您不能更改数组的长度或其元素类型,因为这些会更改对象类型。请注意,由于只能由初始值设定项列表初始化,因此您必须列出此类列表中的每个元素,因此这对于大型数组来说会变得笨拙。std::array

#include <iostream>
#include <array>
#include <memory>

std::array< const int, 3> a{ 1,2,3 };
void mutate()
{
    std::destroy_at(&a);  // call destructor. not needed unless array elements have destructors
    std::construct_at(&a, std::array<const int, 3> {2, 3, 4});
}

int main()
{
    mutate();
    std::cout << a[0] << '\n';
}

评论

0赞 fisherwebdev 10/2/2023
谢谢!虽然我可能会重新思考我是如何按照@paddy建议的思路来解决这个问题的,但我非常感谢这个对我所问的实际问题的回答。✌️
1赞 doug 10/2/2023
@fisherwebdev我也非常同意@paddy的评论,但想具体解决你的问题,因为这些技术并不为人所知,而且可能很有用std::array