使用 std::fill 填充多维数组的安全方法是什么?

发布于 2024-09-27 21:24:58 字数 182 浏览 1 评论 0 原文

这是我正在使用的:(

class something
{
   char flags[26][80];
} a;

std::fill(&a.flags[0][0], &a.flags[0][0] + 26 * 80, 0);

更新:我应该早点说清楚我在课堂上使用它。)

Here is what I am using:

class something
{
   char flags[26][80];
} a;

std::fill(&a.flags[0][0], &a.flags[0][0] + 26 * 80, 0);

(Update: I should have made it clear earlier that I am using this inside a class.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

悲凉≈ 2024-10-04 21:24:59

将数组初始化为 0 的简单方法在定义中:

char flags[26][80] = {};

如果您想使用 std::fill,或者您想重置数组,我发现这更好一点:

char flags[26][80];
std::fill( &flags[0][0], &flags[0][0] + sizeof(flags) /* / sizeof(flags[0][0]) */, 0 );

以数组大小表示的 fill 将允许您更改尺寸并保持 fill 不变。在您的情况下, sizeof(flags[0][0])1 (sizeof(char)==1),但您可能如果您想随时更改类型,请将其保留在那里。

在这种特殊情况下(标志数组--积分类型)我什至可以考虑使用memset,即使它是最不安全的替代方案(这如果数组类型更改为非 pod 类型,则会中断):

memset( &flags[0][0], 0, sizeof(flags) );

请注意,在所有三种情况下,数组大小仅输入一次,编译器会推断出其余部分。这更安全,因为它为程序员留下了更少的错误空间(改变一个地方的大小,忘记在其他地方)。

编辑:您已经更新了代码,并且由于该数组是私有的并且您正在尝试从外部初始化它,因此它不会编译。根据您的类是否实际上是一个聚合(并且希望保留它本身)或者您是否想向类添加构造函数,您可以使用不同的方法。

const std::size_t rows = 26;
const std::size_t cols = 80;
struct Aggregate {
   char array[rows][cols];
};
class Constructor {
public:
   Constructor() {
      std::fill( &array[0][0], &array[rows][0], 0 ); // [1]
      // memset( array, 0, sizeof(array) );
   }
private:
   char array[rows][cols];
};
int main() {
   Aggregate a = {};
   Constructor b;
}

即使数组应该是公共的,使用构造函数可能是更好的方法,因为它将保证数组在类的所有实例中正确初始化,而外部初始化取决于用户代码不要忘记设置初始值。

[1] 正如 @Oli Charlesworth 在评论中提到的,使用常量是解决必须在多个位置声明(并保持同步)大小问题的另一种解决方案。我在这里使用了一种不同的组合的方法:可以通过请求二维数组之外的一行第一列的地址来获取指向二维数组之外的第一个字节的指针。我使用这种方法只是为了表明它可以完成,但它并不比 &array[0][0]+(rows*cols) 等其他方法更好

The simple way to initialize to 0 the array is in the definition:

char flags[26][80] = {};

If you want to use std::fill, or you want to reset the array, I find this a little better:

char flags[26][80];
std::fill( &flags[0][0], &flags[0][0] + sizeof(flags) /* / sizeof(flags[0][0]) */, 0 );

The fill expressed in terms of the array size will allow you to change the dimensions and keep the fill untouched. The sizeof(flags[0][0]) is 1 in your case (sizeof(char)==1), but you might want to leave it there in case you want to change the type at any point.

In this particular case (array of flags --integral type) I could even consider using memset even if it is the least safe alternative (this will break if the array type is changed to a non-pod type):

memset( &flags[0][0], 0, sizeof(flags) );

Note that in all three cases, the array sizes are typed only once, and the compiler deduces the rest. That is a little safer as it leaves less room for programmer errors (change the size in one place, forget it in the others).

EDIT: You have updated the code, and as it is it won't compile as the array is private and you are trying to initialize it externally. Depending on whether your class is actually an aggregate (and want to keep it as such) or whether you want to add a constructor to the class you can use different approaches.

const std::size_t rows = 26;
const std::size_t cols = 80;
struct Aggregate {
   char array[rows][cols];
};
class Constructor {
public:
   Constructor() {
      std::fill( &array[0][0], &array[rows][0], 0 ); // [1]
      // memset( array, 0, sizeof(array) );
   }
private:
   char array[rows][cols];
};
int main() {
   Aggregate a = {};
   Constructor b;
}

Even if the array is meant to be public, using a constructor might be a better approach as it will guarantee that the array is properly initialized in all instances of the class, while the external initialization depends on user code not forgetting to set the initial values.

[1] As @Oli Charlesworth mentioned in a comment, using constants is a different solution to the problem of having to state (and keep in synch) the sizes in more than one place. I have used that approach here with a yet different combination: a pointer to the first byte outside of the bidimensional array can be obtained by requesting the address of the first column one row beyond the bidimensional array. I have used this approach just to show that it can be done, but it is not any better than others like &array[0][0]+(rows*cols)

烙印 2024-10-04 21:24:59

使用 std::fill 填充多维数组的安全方法是什么?

简单的默认初始化是使用花括号初始化

char flags[26][80]{};

上面的代码会将 flags 中的所有元素初始化为默认字符。


使用 std::fillstd::fill_n 填充二维数组

但是,为了提供不同的值来初始化上述内容是不够的。选项是 std::fillstd::fill_n。 (假设数组flags在你的类中是public

std::fill(
   &a.flags[0][0],
   &a.flags[0][0] + sizeof(a.flags) / sizeof(a.flags[0][0]),
   '0');

// or using `std::fill_n`
// std::fill_n(&a.flags[0][0], sizeof(a.flags) / sizeof(a.flags[0][0]), '1');

为了将其推广到具有任何初始化值的任何类型的任何二维数组,我会建议模板化函数,如下所示。这也将避免数组中总元素的 sizeof 计算。

#include <algorithm> // std::fill_n, std::fill
#include <cstddef>   // std::size_t

template<typename Type, std::size_t M, std::size_t N>
constexpr void fill_2D_array(Type(&arr2D)[M][N], const Type val = Type{}) noexcept
{
   std::fill_n(&arr2D[0][0], M * N, val);
   // or using std::fill
   // std::fill(&arr2D[0][0], &arr2D[0][0] + (M * N ), val);
}

现在您可以初始化您的flags,例如

fill_2D_array(a.flags, '0'); // flags should be `public` in your class!

(观看在线直播)< /strong>


使用 std::fillstd::fill_n 进行 3-D 数组填充

在上面的模板函数中再添加一个非模板大小参数,这可以带来以及 3d 数组

#include <algorithm> // std::fill_n
#include <cstddef>   // std::size_t

template<typename Type, std::size_t M, std::size_t N, std::size_t O>
constexpr void fill_3D_array(Type(&arr3D)[M][N][O], const Type val = Type{}) noexcept
{
   std::fill_n(&arr3D[0][0][0], M * N * O, val);
}

在线观看直播)< /强>

What is the safe way to fill multidimensional array using std::fill?

The easy default initialization would be using braced inilization.

char flags[26][80]{};

The above will initialize all the elements in the flags to default char.


2-D Array filling using std::fill or std::fill_n

However, in order to provide different value to initialize the above is not enough. The options are std::fill and std::fill_n. (Assuming that the array flags is public in your class)

std::fill(
   &a.flags[0][0],
   &a.flags[0][0] + sizeof(a.flags) / sizeof(a.flags[0][0]),
   '0');

// or using `std::fill_n`
// std::fill_n(&a.flags[0][0], sizeof(a.flags) / sizeof(a.flags[0][0]), '1');

To generalize this for any 2d-array of any type with any initializing value, I would suggest a templated function as follows. This will also avoid the sizeof calculation of the total elements in the array.

#include <algorithm> // std::fill_n, std::fill
#include <cstddef>   // std::size_t

template<typename Type, std::size_t M, std::size_t N>
constexpr void fill_2D_array(Type(&arr2D)[M][N], const Type val = Type{}) noexcept
{
   std::fill_n(&arr2D[0][0], M * N, val);
   // or using std::fill
   // std::fill(&arr2D[0][0], &arr2D[0][0] + (M * N ), val);
}

Now you can initialize your flags like

fill_2D_array(a.flags, '0'); // flags should be `public` in your class!

(See Live Online)


3-D Array filling using std::fill or std::fill_n

Adding one more non-template size parameter to the above template function, this can be brought to 3d-arrays as well

#include <algorithm> // std::fill_n
#include <cstddef>   // std::size_t

template<typename Type, std::size_t M, std::size_t N, std::size_t O>
constexpr void fill_3D_array(Type(&arr3D)[M][N][O], const Type val = Type{}) noexcept
{
   std::fill_n(&arr3D[0][0][0], M * N * O, val);
}

(See Live Online)

烦人精 2024-10-04 21:24:59

它是安全的,二维数组是数组的数组。由于数组占用连续的存储空间,因此整个多维事物也将占用连续的存储空间。所以是的,它没问题,安全且便携。假设您没有询问其他答案所涵盖的样式(因为您使用的是标志,我强烈推荐 std::vector > myFlags(26)

it is safe, a two-dimensional array is an array of arrays. Since an array occupied contiguous storage, so the whole multidimensional thing will too. So yeah, it's OK, safe and portable. Assuming you are NOT asking about style, which is covered by other answers (since you're using flags, I strongly recommend std::vector<std::bitset<80> > myFlags(26))

不…忘初心 2024-10-04 21:24:59
char flags[26][80];
std::fill((char*)flags, (char*)flags + sizeof(flags)/sizeof(char), 0);
char flags[26][80];
std::fill((char*)flags, (char*)flags + sizeof(flags)/sizeof(char), 0);
幻梦 2024-10-04 21:24:59

char[80] 应该替代真正的字符串类型吗?在这种情况下,我建议如下:

std::vector<std::string> flags(26);
flags[0] = "hello";
flags[1] = "beautiful";
flags[2] = "world";
// ...

或者,如果您有支持初始化列表的 C++ 编译器,例如最近的 g++ 编译器:

std::vector<std::string> flags { "hello", "beautiful", "world" /* ... */ };

Is char[80] supposed to be a substitute for a real string type? In that case, I recommend the following:

std::vector<std::string> flags(26);
flags[0] = "hello";
flags[1] = "beautiful";
flags[2] = "world";
// ...

Or, if you have a C++ compiler that supports initialization lists, for example a recent g++ compiler:

std::vector<std::string> flags { "hello", "beautiful", "world" /* ... */ };
浪漫人生路 2024-10-04 21:24:59

问题不在于您无法通过将适当的指针传递给函数来填充二维数组。很明显,如果您编写一个函数,例如,采用 T* 和 rows*cols 计数并假设一组连续的 T,它将编译并愉快地填充二维数组。该函数对二维数组的结构一无所知。它只是获取一个指针和长度。

问题是编译器知道结构。它知道,如果将指针传递给二维数组中的元素,则唯一可以根据语言修改的值是指针寻址的子数组中的值。因此编译器可以假设该子数组之外的所有其他值都不会改变。语言保证了这一点。编译器优化器可以利用它来加速代码。

这与signed int 溢出问题类似。虽然有符号溢出在二进制补码算术中得到了很好的定义,但编译器可以自由地假设它永远不会发生,因为它是被语言禁止的。这提供了优化机会,编译器可以利用它来加速代码。

所以不,您不能使用 std::fill 来初始化多维数组的内容。 C++ 语言对于指针允许指向的内容相当严格。即使没有取消引用它们!以下代码显示 std::fill 在尝试初始化整个数组时失败。它还表明,简单地将指针增加到最后一个元素之后就是 UB。

#include <algorithm>

constexpr bool foo()
{
    char matrix[2][10]{};
    std::fill(&matrix[0][0], &matrix[0][10], 0);    // works
    std::fill(&matrix[1][0], &matrix[1][10], 0);    // works
    // std::fill(&matrix[0][0], &matrix[1][10], 0);    // fails, UB detected
    char* pc = &matrix[0][10];  // Legal. points to one past the last element of the lower, 10 char array.
    // pc = &matrix[0][10]+1;  // UB. points to two past the last element of the lower, 10 char array.
    return true;
}

constexpr bool x = foo();

int main()
{
    return x;
}

编译器资源管理器中的代码

The problem isn't that you can't fill a 2D array by passing appropriate pointer(s) to a function. It's pretty clear that if you write a function that, for instance, takes a T* and a count of rows*cols and assumes a contiguous set of T's, it will compile and happily fill the 2D array. The function doesn't know anything about the structure of the 2D array. It just gets a pointer and length.

The problem is the compiler knows the structure. And it knows that if you pass a pointer to an element in the 2D array, the only values that can be modified according to the language, are the ones in the sub-array that the pointer addressed. So the compiler can assume all other values outside of that subarray will not be altered. The language guarantees that. And compiler optimizers can take advantage to speed up code.

This is similar to the problem of signed int overflow. While signed overflow is well defined in two's complement arithmetic, compilers are free to assume it never happens because it is prohibited by the language. This opens up optimization opportunities and compilers take advantage to speed up code.

So no, You can't use std::fill to initialize the contents of a multi-dimensional array. The C++ language is rather strict on what pointers are allowed to point to. Even without de-referencing them! The following code shows std::fill failing when trying to initialize the entire array. It also shows that simply incrementing the pointer to one past the last element is UB.

#include <algorithm>

constexpr bool foo()
{
    char matrix[2][10]{};
    std::fill(&matrix[0][0], &matrix[0][10], 0);    // works
    std::fill(&matrix[1][0], &matrix[1][10], 0);    // works
    // std::fill(&matrix[0][0], &matrix[1][10], 0);    // fails, UB detected
    char* pc = &matrix[0][10];  // Legal. points to one past the last element of the lower, 10 char array.
    // pc = &matrix[0][10]+1;  // UB. points to two past the last element of the lower, 10 char array.
    return true;
}

constexpr bool x = foo();

int main()
{
    return x;
}

Code in Compiler Explorer

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文