【问题标题】:Durability of string's reverse iterators for self appending用于自附加的字符串反向迭代器的持久性
【发布时间】:2018-10-31 13:02:21
【问题描述】:

问题:

假设我有一个字符串,我想生成一个新字符串,其中包含原始字符串及其反向连接。

以下内容是否保证有效?

auto pq = [](std::string &s){
    s.reserve(2*s.size());
    s.append(s.rbegin(), s.rend());
};

我看到reserve 应该适当地设置capacity。但是,对反向迭代器应用append 会导致这些迭代器失效吗?

附加背景:

我的 C++.11 副本(与 C++.17 draft 具有相同的语言),在 §[string.capacity]

中说

void reserve(size_type res_arg=0);

  1. 成员函数reserve() 是一个指令,它通知basic_string 对象计划的更改 大小,以便它可以相应地管理存储分配。
  2. 效果:reserve()之后,capacity()大于等于reserve的参数。 [ 注意: 调用 reserve()res_arg 参数小于 capacity() 实际上是一个非绑定收缩请求。 与res_arg <= size() 的调用实际上是一个不具约束力的缩小以适应的请求。 — 结束说明 ]​​i>
  3. 抛出: 如果res_arg > max_size().227,则为 length_error

227) reserve() 使用 allocator_traits::allocate() 可能会引发适当的异常。

虽然,§[string.append] 说

basic_string&
append(const charT* s, size_type n);

  1. 要求: s 指向至少包含n 元素的charT 的数组。
  2. 抛出: length_error if size() + n > max_size().
  3. 效果:函数将*this控制的字符串替换为长度为size() + n的字符串 其第一个 size() 元素是由 *this 控制的原始字符串的副本,其剩余 元素是s 的初始n 元素的副本。
  4. 返回: *this

【问题讨论】:

  • 我很确定,只要容量足够大,迭代器在附加到字符串时不会失效,并且这将扩展到反向迭代器;如果我是对的,这段代码将生成一个回文。但我绝对不确定。

标签: c++ string iterator language-lawyer


【解决方案1】:

编辑

我不再相信这个答案是正确的,请参阅 cmets。但为了保存有价值的 cmets,我将答案留在那里。

最直接的 append 看起来像这样:

template<class T>
void string::append(T begin, T end) {
    reserve(size() + std::distance(end, begin));
    while (; begin != end; ++begin) {
       *this += *begin;
    }
}

由于保留后迭代器不应失效,因此在整个函数期间,begin 对解引用和递增仍然有效。

在上面的示例中,reserve 保证在我可以访问的最新草稿 (http://eel.is/c++draft/string.capacity) 中是无操作的:

(24.3.2.4) 无效保留(size_type res_arg);

#Effects:通知 basic_string 计划的大小更改的指令,以便可以相应地管理存储分配。 在reserve()之后,capacity() 大于或等于 如果发生重新分配,则保留;并且等于之前的值 容量()否则。当且仅在此时发生重新分配 如果当前容量小于reserve()的参数

【讨论】:

  • @DanielH 他在哪里说他想要的?他只是提供了最可能的实现——不建议自己实现
  • 您能否将您的引文添加到您的答案中,以及指向来源的链接?
  • 请注意,传递给 append 的迭代器是 input 迭代器,因此在使用 distance 之前无法使用它们。
  • @jxh 不能用于输入迭代器,因为它们很容易成为单通道(例如,iostream 输入迭代器是单通道迭代器)
  • @MarkRansom 在typename std::iterator_traits&lt;Iter&gt;::iterator_category 上使用标签调度来确定您拥有什么样的迭代器,并尽可能提高效率。
【解决方案2】:

这不是您实际调用的std::string::append 的重载。您拨打的电话是template&lt;class InputIterator&gt; basic_string&amp; append(InputIterator first, InputIterator last);。在那里,标准说 ([string.append]/21) 相当于在附加之前构造一个新字符串:

效果:相当于append(basic_­string(first, last, get_­allocator()))

注意这里,构造函数调用basic_­string(first, last, get_­allocator()) 在调用append 的不同重载之前创建了一个临时字符串,因此在另一个append 中发生的任何重新分配都是无关紧要的.这意味着,即使没有先调用reserve,这也应该是安全的。

请注意,不能保证 string 完全以这种方式实现;该标准说“等效于”,而不是“实施为”。实现可以做任何具有相同结果的事情,但在这种情况下,“相同的结果”意味着它仍然需要使用派生自相同字符串的迭代器。

【讨论】:

  • @jxh 如果标准中的这些引用是正确的,那么这里有 三个 调用。第一个有两个迭代器构造一个新字符串,然后用那个字符串调用第二个,然后用字符串数据和长度调用第三个。没有矛盾。
  • @jxh 请注意,不能保证调用与指定的完全一致;正如我在别处指出的那样,libstdc++ 使用std::string::replace 来实现这个实际调用,而不是使用不同的append 重载。该标准保证可见效果是相同的,所以这应该有效。
  • 更新了我的答案以解释这些细节,而不仅仅是在 cmets 中。
猜你喜欢
  • 2016-09-08
  • 2023-03-28
  • 1970-01-01
  • 2021-01-15
  • 2018-10-10
  • 2021-04-01
  • 1970-01-01
  • 2019-07-10
  • 1970-01-01
相关资源
最近更新 更多