问题 容器优化:为什么STL容器方法参数不再使用allocator :: const_reference typedef?


在你读之前:  const_reference 是typedef,不需要 const T& 正如你所看到的那样 std::vector<bool>::const_reference = bool。请在阅读其他内容时牢记这一点,以便正确理解(正如值得一提的,这对很多人来说很难)。


我想将STL容器用于简单类型(例如 int)并发现他们使用次优 const T&  “反模式”  - 它适用于大类,但在没有内联时对于简单/基本类型是次优的 - 考虑嵌入式系统,例如在ARM / ATSAM4L上,实例化。

问题是:为什么例如 vector::push_back 重新设计的论点 (const value_type&) 代替 (Allocator::const_reference)自C ++ 11以来? 通用应该是相同的 allocator,但以另一种方式这样做将有助于我为定义的基本类型编写自定义分配器(或模板特化) const_reference 作为类型本身(见 vector<bool>::const_reference = bool)。

问题2: 有没有 适配器 能为我做那件事的班级?

像这样的东西:

template<class T, class base = std::vector<T> >
  class myvect: protected base {
public:
    typedef T const_reference;
    void push_back(const_reference value) {
        base::push_back(value); }}

最终用法将是这样的:

typedef void (*action_t)(void*,int);
extern "C" void work(void *, action_t);
work(&vect, (action_t)&vect::push_back);

注意: 忽略以前代码块中可能的转换问题,我希望你有这个想法。)

编辑:  vector::const_reference 直接定义为 const value_type& 但在我看来应该被定义为 Alloc::const_reference 然后可以很容易地改变(vector<int, MyAllocator<int> >)。 (这在C ++ 11中有所改变,它被定义为Alloc :: const_reference但现在是const value_type&)

编辑:  func(const T&) 有时被描述为 “反模式” 这里是stackoverflow,因为它对于没有内联的基本类型来说是次优的(是的,编译器生成最佳代码,即使对于 func(const int&) 如果内联,但就是这样 如果func(int) 会做得更好)


结论: 问题似乎在于行为 const 因此 const T&const 并不是真的意思 不得改变 但 不要换 因此 const T& 需要(并明确定义和使用) 作为返回类型 对于很多方法。创建 自定义适配器类 似乎是优化的最佳方式,它似乎被广泛接受 std::vector<bool> 是奇怪的例外,应该在单独的类中(例如 dynamic_bitset)。


4133
2017-07-30 07:20


起源

这两种类型有什么区别?恕我直言,std :: vector <T> :: const_reference等同于std :: vector <T> :: value_type const&。 vector <bool>非常特殊,定义了const_reference,因为类型本身会使向量接口无效,例如front()必须返回一个引用。您的优化将无法做到,例如&v.front()获取指向内存的指针,并使用向量作为C风格动态数组的替代。 - Jens
不 const value_type& 和 const_reference 完全相同的类型? - stijn
我希望无论如何代码都是内联的,并且结果asm在两者之间是相同的 std::vector<int>::push_back(const int&) 和你期望的签名 std::vector<int>::push_back(int)。 - Jarod42
你需要稍微改一下你的问题,这里有一点帮助:人们不明白你想做一个“政策”,请在你的问题中进一步解释你希望“reference_type”是定制的。在人们看来,reference_type是由内部实现修复的,因此他们假设你正在谈论两件完全相同的事情。你的问题只是略显不合理,你可以解决它。 - v.oddou
@ v.oddou:嗯,我可以理解你说的话(以及为什么我已经在那个问题上得到-2),但是想到另一种方式:我已经明确说过了 const_reference = T 会是优化,我怎么能做得更好? “嘿,大家好几次,const_reference是typedef并再次思考,并不总是需要等同于const T&,你可以在vector <bool>中看到!” ?? - firda


答案:


它的评论有点长,所以我会用“答案”来谈论它,但实际上这不是答案。

你想要的功能,改写:

您希望te能够避免某些方法中的“通过const引用”惯用法,以避免小型类型的指针操作所产生的“减速”,而不是直接复制。对于push_back尤其如此,如果push_back有一个具有可配置类型的参数(通过模板意味着在容器类型中,比如allocator模板参数),那么它可以让你感觉良好。

例如,通过使用与allocator模板参数相同的方法,可以提供所需的功能,可以使用typename指定“push back”参数中使用的类型。

但它不是那样设计的,因为它似乎是一种复杂性,根本不值得。 由于编译器优化会消除基本类型的大量开销,因此不值得,因为静态分析更容易在基本类型上运行。

其次,通过引用传递int与在方法参数中复制它的“丢失时间”被认为是平台特异性,无法在“虚拟机”级别(语言编写者必须自己放置的级别)进行验证)。更不用说这里的“过早优化”会导致坦率的代码膨胀获得,作为一般目的的合理权衡,它被彻底驱逐。这也是因为在设计时,您会想到基本类型,如稀有性和可能包含的可能类型的无穷大。

PS: 最后,使用typedef作为分配器的一部分似乎是一个非常糟糕的责任分离,拥有自己的模板参数似乎更清晰,因为它与分配无关,而是与实现选择的特定方法有关。容器。

PS2 /编辑: 我想补充一下,正如评论中所说的那样 vector<bool>::const_reference 不能作为可能做的例子,因为这是标准中的错误,实际上是对STL的侵犯 需求 容器定义的 ..::reference 如 T&。问题的进一步证明是 vector<bool>::reference (不是const)不是bool,它是一个未指定的类(_Bit_reference 在gcc中,它作为一个代码,用于访问和变换一个位,包含在向量中,它将作为“虚拟”bool值的存储支持,使其看起来像现有的一样。这些奇怪的东西不能移植到通用向量,因为它们不符合宽STL要求。但它并没有使正交方法失效,就像我提到的那样 pass_for_copy_param_type 这可能是一个可参数化的新typedef,并且在今天编写“const value_type&”的某些地方(合适的地方)用于替换。但这正是我之前提到过的代码膨胀。我确信这么少的人肯定不值这么多。


6
2017-07-30 08:03



好的答案,我会考虑接受它。我不得不在“严肃工程师”方面与你意见不一致 - 你有没有为64kB内存的微处理器编写程序?甚至8kB?请再想一想。 - firda
然后我会编辑它 - v.oddou
我接受了这个答案(而不是来自juanchopanza的答案),因为它解决了整个问题(包括所有部分)而不是简单地回答标题。谢谢 - firda


改变意义 const_reference 会破坏返回a的方法的语义 const 参考:

std::vector<int> my_vector(100);
 // this needs an lvalue ref
my_vector[42]++;

// I want to track this value
std::vector<int>::const_reference i = my_vector[3];
  // I expect i to be be 1 after this
my_vector[3]++;

4
2017-07-30 08:00



+1很棒的答案。 - Mehrdad
const_reference = T,reference = T&。 my_vector [42] ++很好,你的第二个例子使用别名(我认为它应该被描述为volatile_const_reference),这不是一个好习惯。 - firda
@firda无论是好的做法还是意见问题。不过,您可以期望能够使用标准库容器 const_reference。 - juanchopanza
嗯,问题是“为什么它的设计是这样的+我们有一些适配器做我想要的吗?”因此你的答案是“因为接受了别名是允许的,所以前面()和索引可以用来跟踪存储的值(不是复制或不可变对象......)我可以在这里考虑可变属性)“(第二个问题未答复)。我认为这实际上是'const'意味着什么以及如何使用它的问题(const并不意味着'永远不会改变',而是'不写''。 ...好吧,我认为这里有两个回答值得接受。我今天晚些时候会回复它 - firda


允许更改const_reference的类型会破坏与std :: vector和容器要求的兼容性。该标准明确假定const_reference是一个引用并使用它多次,例如data()和front()的语义。在23.3.6.4中,它表示“对于非空向量,data()==&front()”。这使得您的修改无效,因为修改后,front()将不再返回对已分配内存的引用,因此地址会有所不同。实际上,它将是临时对象的地址。

我还认为大多数编译器都会优化使用const&away的任何额外成本。

std :: vector <bool>是一个不好的例子,因为它不是STL意义上的容器模型。大多数人都同意它不应该在标准中定义,例如 https://isocpp.org/blog/2012/11/on-vectorbool


2
2017-07-30 07:55



再次:&front()(或&vect [0])现在是常见的做法,我可以理解。我的更改不会改变标准向量与标准(和常见)分配器的行为。所有我建议的是重塑矢量模板,它的行为可以通过分配器改变(是的,&myvect.front()会生病,但我的vect不是std :: vect <int>而是std :: vect <int ,MyAllocator> with MyAllocator :: const_reference = int) - firda
好吧,每个std :: vector <T,Alloc <T>>都必须符合标准中的语义,这明确指出data()==&front()。这当然可以改变,但老实说,我没有看到这样做的好处。你能详细说明这个好处以及为什么这实际上是一种反模式?我从来没有听说过这个,但我不是一个嵌入式程序员。我觉得编译器会优化基本类型的任何开销。 - Jens
std :: vector <bool>不符合语义,为什么你坚持认为std :: vector <T,MyAlloc>必须是? - firda
因为这是标准规定的以及我(和大多数程序员)所期望的。我希望能够编写适用于所有向量的通用代码,独立于我们认为分配器的实现细节。它们提供了一个策略来自定义向量的内部细节,但不提供它在外部的工作方式。大多数人都不鼓励使用vector <bool>,因为它看起来像矢量但却打破了语义。即使是标准委员会的成员也这样认为: isocpp.org/blog/2012/11/on-vectorbool - Jens
@firda:因为标准明确表示vector <bool>是一个例外。 - MSalters