为啥 C++11 会从 std::vector 填充构造函数的原型中移除默认值?

Posted

技术标签:

【中文标题】为啥 C++11 会从 std::vector 填充构造函数的原型中移除默认值?【英文标题】:Why did C++11 remove the default value from the prototypes of std::vector's fill constructor?为什么 C++11 会从 std::vector 填充构造函数的原型中移除默认值? 【发布时间】:2018-02-06 03:41:52 【问题描述】:

在 C++98 中,std::vector 的填充构造函数的原型具有初始值设定项的默认值。

explicit vector (size_type n, const value_type& val = value_type(),
                 const allocator_type& alloc = allocator_type());

C++11 使用两个原型。

explicit vector (size_type n);
         vector (size_type n, const value_type& val,
                 const allocator_type& alloc = allocator_type());

(在 C++14 中,填充构造函数再次更改,但这不是这个问题的重点。)

参考链接是here。

为什么 C++11 弃用了默认初始化值 value_type()

顺便说一句,我尝试用clang++ -std=c++11编译下面的代码,它发出了一个错误,这意味着值类型仍然需要有一个像S() 这样的默认构造函数,即可以默认构造。

#include <vector>

struct S 
    int k;
    S(int k) : k(k)  // intentionally remove the synthesized default constructor
;

int main() 
    std::vector<S> s(5); // error: no matching constructor

【问题讨论】:

您在最后展示的示例在 C++11 之前也不会工作,因为 S 不是默认可构造的。而且 C++11 并没有弃用默认值,那个单一的构造函数被另外两个替换了。 默认值是邪恶的。 @Praetorian 是的,我应该说 C++11 删除了构造函数原型中的默认值。 @Jesper Juhl 你得详细说明 【参考方案1】:

C++98 取了一个原型对象,然后复制了 n 次。默认情况下,原型是一个默认构造的对象。

C++11 版本构造 n 个默认构造的对象。

这消除了 n 个副本并将其替换为 n 个默认构造。此外,它避免了构建原型。

假设你的班级看起来像这样:

struct bulky 
  std::vector<int> v;
  bulky():v(1000)  // 1000 ints
  bulky(bulky const&)=default;
  bulky& operator=(bulky const&)=default;

  // in C++11, avoid ever having an empty vector to maintain
  // invariants:
  bulky(bulky&& o):bulky() 
    std::swap(v, o.v);
  
  bulky& operator=(bulky&& o) 
    std::swap(v,o.v);
    return *this;
  
;

这是一个总是拥有1000ints缓冲区的类。

如果我们随后创建bulky 的向量:

std::vector<bulky> v(2);

在 C++98 中,这分配了 3 次 1000 个整数。在 C++11 中,这仅分配 2 乘以 1000 个整数。

另外,C++98 版本要求类型是可复制的。 C++11中有不可复制的类型,比如std::unique_ptr&lt;T&gt;,使用C++98签名不能生成默认构造的唯一指针vector。 C++11签名没问题。

std::vector<std::unique_ptr<int>> v(100);

如果我们还有 C++98 版本,上面的方法就行不通了。

【讨论】:

我可能会恢复解释,因为正确性胜过效率 :D 但是,这是对好问题的好答案。 @SergeyA 我不确定何时确切地要求在向量中从传递给向量的类型放宽,而是与所使用的确切方法耦合。效率差异肯定出现在 C++11 中;我不确定从技术上讲,放宽的要求是否有效。所以我详细说明了我所知道的,然后提到在 C++11/14 的某个地方它也导致了对类型的放宽要求(实际上在 C++11 中,我不确定标准是否真的在 C+ 之前放宽了它+14,因为我不记得了) 很好的解释,我只是不明白为什么在 c++98 版本中原型对象不用作第一个元素(即,在您的示例中也只会分配 2000 个整数) @tobi303 原型存在于堆栈中,在参数中。对象存在于由向量管理的内存中。向量不管理参数,因此它不能是向量中的第一个对象。 C++ 具有值语义,该对象具有确定的位置。在 C++11 中,他们可以将其设为右值或左值参数并从中移出,但我疯狂的 bulky 类型即使在移出时也拒绝为空,因此它也无济于事。 @Yakk 在 C++11 中放宽了。 C++98 中的explicit vector(size_type n, const T&amp; value = T(), const Allocator&amp; = Allocator()); 被拆分为两个explicit vector(size_type n); vector(size_type n, const T&amp; value, const Allocator&amp; = Allocator());。基本上,在 C++98 中,将元素放入向量中的唯一方法是复制它们。【参考方案2】:

构造函数被分成两部分的原因是为了支持“只移动”类型,例如unique_ptr&lt;T&gt;

这个构造函数:

vector(size_type n, const T& value, const Allocator& = Allocator());

要求T 是可复制构造的,因为必须从value 复制n Ts 才能填充vector

这个构造函数:

explicit vector(size_type n, const Allocator& = Allocator());

要求T 是可复制构造的,只有默认可构造的。

后一个构造函数与unique_ptr&lt;T&gt;一起工作:

std::vector<std::unique_ptr<int>> s(5);

而前一个构造函数没有。

这是进行此更改的提案:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1858.html#23.2.4.1%20-%20vector%20constructors,%20copy,%20and%20assignment

这篇论文有一些基本原理,尽管无可否认有点简洁:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1771.html

Fwiw,resize

void resize(size_type sz, T c = T());

被分成:

void resize(size_type sz);
void resize(size_type sz, const T& c);

出于完全相同的原因。第一个需要默认可构造但不可复制构造(以支持默认可构造移动类型),第二个需要可复制构造。

这些更改并非 100% 向后兼容。对于某些类型(例如引用计数的智能指针),从默认构造对象复制构造与默认构造不同。然而,支持仅移动类型的好处被认为值得为这种 API 破坏付出代价。

【讨论】:

好点。 unique_ptr&lt;T&gt; 是不可复制类的一个很好的例子。不幸的是,只能接受一个答案。 这似乎是真正的答案,因为它解释了为什么这样做了。 (Yakk 目前接受的答案也提到了它,但顺便说一句。) 他们不能只是enable_ifis_copy_constructible吗? @Mehrdad:本来可以的。我判断 container&lt;T&gt;(n) 的 C++11 行为实际上通常更可取,如果确实需要 C++98 行为,那么语法 container&lt;T&gt;(n, T) 仍然可用。并且在绝大多数情况下没有语义差异(大多数用例不会改变)。有一些风险,但有更大的好处。事后看来,我认为这场赌博奏效了。

以上是关于为啥 C++11 会从 std::vector 填充构造函数的原型中移除默认值?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 'std::vector<int> b2;'创建一个 1 元素向量,而不是 2 元素向量?

为啥 std::vector<bool> 没有 .data()?

为啥使用指针的 STL 算法比 std::vector 迭代器快得多?

c++11 std::array vs 静态数组 vs std::vector

启用 C++11 时的 std::vector 性能回归

std::vector 的 std:array 的 SFINAE 不能在 C++11 中编译