【问题标题】:How to implement/use: class for a dynamic array of fixed size (known only at run time)如何实现/使用:固定大小的动态数组的类(仅在运行时知道)
【发布时间】:2021-02-19 12:09:41
【问题描述】:

我正在向自己介绍 C++,可悲的是,对于动态创建的固定大小数组(但大小仅在运行时已知)的支持似乎在 C++ 中非常差,因为 new[] 可以t 使用用户设置的参数调用任意用户指定的构造函数。

考虑class A,它有许多构造函数,每个构造函数都有一些参数。假设没有参数的构造函数是无用的(如果我基本上不需要它,我不想写一个)。我想以下内容无关紧要,但是,以防万一:假设A 仅包含一个可能很大的std::vector<Internal>Internal 是一个私有类,TS 参数化A)和就数据成员而言,是一个整数计数器。另外,A 是参数化的。

假设我们希望An 实例作为数组连续存储在内存中,其中n 在运行时确定并在之后保持不变。我们希望能够通过将参数传递给A 或类似的构造函数的单个调用来创建和初始化结构。所以数组中的每个实例都得到相同的,但程序化的初始化。编辑:对不起,我并不是说我想要 O(1) 初始化,因为那是不可能的,我只想要 O(n) 初始化,但是这样我就可以在一个语句中创建数组。即,这样我就不必为我创建的每个数组编写一个初始化循环。

std::vector<A<T,S>> 是一个可能但不是最佳的解决方案,但假设我们不能忍受低效率。 (请记住,std::vector 支持调整大小。)

如何实现和/或使用具有良好 API 的高效解决方案?

我更喜欢不重新实现一半标准库的解决方案,即考虑 C++20 功能和可用于实现的标准库。另外,不要让我违反 C++ 别名 rules

一个可能相关的问题是为什么标准库中缺少这样一个“fixed_size_vector”类?

(顺便说一句:没关系,但请不要说“只使用向量”,因为在这种情况下,我确实会采用提到的次优解决方案,因为性能对我的玩具程序并不重要,但在现实世界中,性能有一天会很重要,我想做好准备。编辑:我确实不是的意思是我想优化我的玩具程序,而是我指的是有一天我将不得不优化其他一些程序。)

编辑:回答一些评论者:包装 std::vector 可以提供正确的抽象,但它会不必要地低效。一条评论链接了一个问题,其最佳答案很好地解释了这一点:

dynarray 比 vector 更小更简单,因为它不需要 管理单独的大小和容量值,并且不需要 存储分配器

dynarray 这里是对 stdlib 的提议添加,这似乎是我想要的,除了它还应该依赖特殊的编译器支持来支持它的某些语义)。当然,与 std::vector 相比,这种差异在大多数情况下并不重要,但如果我能够简单地使用正确的工具来完成这项工作,那仍然是一件好事。

【问题讨论】:

  • 只需使用std::vector(即使您说不这样做 - 它也有效)。
  • 请不要说“只使用矢量” 但这就是解决方案。它具有您想要/需要的所有界面。如果您唯一关心的是有人可以扩展向量,那么将 vector 包装在您自己的类中,并且不要提供任何可能增加向量大小的函数。您也可以只创建一个const vector<YourType> 并使用您想要的所有对象构建它。向量中的对象不会是const,但由于向量是,如果您尝试更改向量本身的状态,则会收到错误消息。
  • “在现实世界中,性能总有一天会很重要,我想做好准备” 你绝对会想要放弃这种心态。现实世界的代码首先是关于可维护性和可伸缩性的。你不是为电脑编程,你是为人编程。优化对瓶颈和明显的清理很重要;但是,如果您尝试对所有内容进行微优化,那么您作为 C++ 开发人员的职业生涯将不会很长
  • 关于您的编辑:您似乎不明白效率到底是什么以及它与std::vector 的关系。您可以预先在向量中保留空间,这相当于为n 对象分配存储空间在调用构造函数之前。我建议你真正了解std::vector 的能力及其工作原理,而不是忽视正确答案
  • "但假设我们不能忍受低效率。(记住 std::vector 支持调整大小。)";如果你不使用它,你就不用付钱。

标签: c++ arrays templates constructor c++20


【解决方案1】:

有一个proposal 为标准添加一个固定容量向量。
请注意,此提案建议在编译时知道容量,因此不适用于您的情况。

也有一些开源库实现了其中之一,例如 Boost 的 static_vector 或 .如果您真的想要一个固定容量的向量,您可以使用现有的开源实现之一。
如果您真的知道自己在做什么,则可以自己编写一个,但超过 99% 的 C++ 用户并非如此。

但是,应该注意的是,在向量上保留()空间可能会产生您想要的效果,并且可能不需要实际的固定容量向量。

【讨论】:

  • 您链接的提案不适用,因为 OP 声明该大小仅在 runtime 时才知道。 OP 唯一真正的解决方案是reserve 前面的空间
【解决方案2】:

由于您提到只有在运行时才知道大小,这正是std::vector 的用途。

template <typename T, typename...Args>
auto make_vector(std::size_t size, const Args&...args) -> std::vector<T>
{
    auto result = std::vector<T>{};
    result.reserve(size); // whatever the known size is
    for (auto i = 0; i < size; ++i) {
        result.emplace_back(args...);
    }
    return result;
}

// Use like:

auto vec = make_vector<std::string>(20, "hello world");

这将为T 类型的size 条目预分配足够的空间,并且循环将使用您传递的任何参数调用T 的构造函数。

请注意:

  • 没有调用其他构造函数。
  • 没有使用额外的内存。
  • 不执行复制或重定位。
  • 由于保证复制省略,返回的向量不会复制(甚至移动) 或更高版本。

无论您使用专门的容器还是其他方式,这样做都是最佳选择。这就是为什么每个有经验的 C++ 开发人员都会告诉你同样的事情:std::vector 解决方案。[2]

注意:上述函数使用const Args&amp;... 进行传播而不是正确的转发引用,因为右值引用可能导致移动后使用错误。[1]


您提到的像fixed_size_vector 这样的专用容器将是以下两种情况之一:

  • 编译时修复了最大尺寸,在这种情况下它对你不起作用,因为你提到尺寸只有在运行时才知道
  • 固定在 runtime 的最大大小,在这种情况下,它会完全按照我上面的建议进行,因为它会预先保留存储空间。李>

不可能在语言级别动态构造N 对象,仅在运行时使用自定义构造函数才知道。 句号。如果序列在编译时已知,而不是运行时已知,则可以这样做。

C++ 是静态编译的,因此我们不能将运行时n 值可变地扩展为一组T{...} 构造函数调用;这根本不可能。这意味着每次都会有一个循环。因此,您可以做的最优化的事情是分配一次n 对象,然后调用T 的构造函数n 次。


[1] 将参数列表传递给序列构造函数的 all 的简写语法在 C++ 中不是一个好的通用解决方案。事实上,它是次要的。这将通过const 左值引用强制复制,或者允许右值——在这种情况下,只有构造的 first 对象将获得有效值,之后的所有内容都将收到一个使用后- 移动对象!想象一下unique_ptrT 的序列。只有第一个实例会得到一个有效的指针,其他的都会收到nullptr

[2] 老实说,您可以对此解决方案进行的唯一真正优化是使用自定义分配器,例如带有堆栈分配内存缓冲区的std::pmr::vector资源。


脚注

强烈建议您克服“效率第一”的心态。大多数开发人员对什么是高效和什么不高效的直觉是错误的;这就是分析器如此重要的原因。推测执行、缓存局部性和流水线在性能方面发挥着重要作用——这些事情远比简单地构造一个动态对象数组复杂得多。

真正的软件是为其他开发人员编写的,而不是为机器编写的。最好拥有可维护和可扩展的代码,并在已通过适当工具确定瓶颈的地方进行优化。

【讨论】:

  • “这要么通过 const 左值引用强制复制” - 我认为这就是我想要的,因为构造函数采用的参数复制起来很便宜?
  • 关于脚注:你们这里的很多人都告诉我应该克服“效率第一”的心态,或类似的,但请注意,我确实要求具有良好 API 的解决方案。 reserve 电话,例如是一个本质上没有必要的额外声明。我不只是将效率置于其他一切之上,我只是想要适合工作的工具(至少在工作出现时)。
  • 您对“效率”进行了几处编辑,而不是干净的 API。我已经看到很多开发人员成为“效率第一”陷阱的受害者。它会导致脆弱的过度设计的不可维护的解决方案,这些解决方案是为了解决特定问题而添加的,但实际上会产生更多问题。有这种心态是不好的。无论如何,我已经更新了我的答案以包含一个满足您要求的简单 API。
猜你喜欢
  • 2013-10-26
  • 1970-01-01
  • 1970-01-01
  • 2020-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-15
相关资源
最近更新 更多