std::vector、默认构造、C++11 和重大更改

Posted

技术标签:

【中文标题】std::vector、默认构造、C++11 和重大更改【英文标题】:std::vector, default construction, C++11 and breaking changes 【发布时间】:2011-08-11 04:21:31 【问题描述】:

我今天遇到了一个非常微妙的问题,我想听听你的意见。

考虑以下花园式的 shared-body-idiom 类:

struct S

    S() : p_impl(new impl) 
private:
    struct impl;
    boost::shared_ptr<impl> p_impl;
;

当您尝试通过以下方式将它们放入向量时,就会出现乐趣:

std::vector<S> v(42);

现在,至少在 MSVC 8 中,v 中的所有元素共享同一个 impl 成员。实际上,造成这种情况的原因是 vector 构造函数:

template <typename T, typename A = ...>
class vector

    vector(size_t n, const T& x = T(), const A& a = A());
    ...
;

在场景下,只有一个S 对象被默认构造,vectorn 元素是从中复制而来的。

现在,在 C++11 中,有右值引用。所以它不能像这样工作。如果vector 构造为

std::vector<S> v(42);

那么很可能,实现将选择默认构造向量内的n 对象,因为复制构造可能不可用。在这种情况下,这将是一个重大变化。

我的问题是:

    C++03 标准是否要求std::vector 必须具有如上定义的构造函数,即。使用默认参数?特别是是否可以保证向量对象的条目被复制而不是默认构造? C++11 标准对这一点有什么看法? 我认为这是 C++03 和 C+11 之间发生重大变化的可能性。有没有调查过这个问题?解决了吗?

PS:请不要对上面的类S 的默认构造函数进行cmets。就是这样或实现某种形式的惰性构造。

【问题讨论】:

这简直令人震惊。编译 C++ 代码但在 C++0X 中赋予它新的含义是我能想到的最糟糕的情况。我很想像使用 DOS 4 或 Windows ME 一样传递这只手。 这应该是威胁吗?您是否将 C++0x 与 Windows ME 进行比较? 恕我直言,从一个名叫“6502”的人那里听到这句话有点好笑…… 【参考方案1】:

C++03 标准是否要求std::vector 必须具有如上所述定义的构造函数,即具有默认参数?特别是可以保证向量对象的条目被复制而不是默认构造?

是的,指定的行为是x 被复制n 次,以便容器被初始化为包含n 元素,这些元素都是x 的副本。


C++11 标准对这一点有什么看法?

在 C++11 中,这个构造函数变成了两个构造函数。

vector(size_type n, const T& x, const Allocator& = Allocator()); // (1)
explicit vector(size_type n);                                    // (2)

除了第二个参数不再有默认参数之外,(1) 的工作方式与 C++03 中的相同:x 被复制 @987654328 @次。

已添加 (2) 代替 x 的默认参数。此构造函数值初始化容器中的n 元素。没有复制。

如果您需要旧的行为,您可以通过向构造函数调用提供第二个参数来确保调用 (1)

std::vector<S> v(42, S());

我认为这是 C++03 和 C++11 之间发生重大变化的可能性。我认为这是 C++03 和 C++11 之间发生重大变化的可能性。有没有调查过这个问题?解决了?

是的,正如您的示例所示,这确实是一个突破性的变化。

由于我不是 C++ 标准化委员会的成员(而且我没有特别关注邮件中与库相关的论文),我不知道这种重大变化被讨论到何种程度。

【讨论】:

@Alexandre:是的,C++0x 标准库实现的行为会有所不同。不过,我不是标准化委员会的成员,所以我不能肯定你描述的场景是否被讨论过。 @6502:更改是一个深思熟虑的决定,而不是疏忽(我认为很难疏忽地添加构造函数重载 :-P)。我不太确定您在“他们在分配时是否也发生了变化......”中问了什么由于引入了 move ctor 和 move op= 到语言,有些事情发生了变化,但我不确定那是怎么回事会特别影响算法。 (另外,我不是 100% 熟悉去年年底“必须采取的隐含行动”辩论的最终决定是什么……)。 对于它的价值,暂时忽略向后兼容性问题,新规范更好,它应该从一开始就以这种方式指定(或者更确切地说,如果值初始化已经存在于其当前1998 年的形式,那么它应该从一开始就以这种方式指定)。不过,我当然理解您对向后兼容性的担忧。 @Alexandre:那么它不是。还有一些其他情况会破坏向后兼容性。他们只是在可以避免或回报不值得时尽量不这样做。但绝对 100% 向后兼容性多年来一直没有出现。 @Nemo:当然,我正在回复 Alexandre C,“该语言要么向后兼容,要么不向后兼容”。事实并非如此,而且公然如此。如果他想要三分法,“要么语言向后兼容;要么不向后兼容;或者 C++03 中的每个格式良好的程序要么在 C++0x 中维护 C++03 保证的所有行为,要么是在 C++0x 中格式错误”,那么他可以使用该三分法而不是二分法。而且它仍然是“不”,只是不那么明显,我同意遗憾的是我们不能拥有第三个。【参考方案2】:

我认为您描述的用例的解决方案不是最佳且不完整,这就是您升级到 C++11 时遇到问题的原因。

C++ 总是关心语义,当你用 C++ 编写程序时,你最好理解你的语义。因此,在您的情况下,您希望创建 N 个对象,但是当您不更改它们时,您希望它们共享相同的内存以进行优化。好主意,但如何做到这一点: 1)复制构造函数。 2)静态实现+复制构造函数。 您是否考虑过这两种解决方案?

假设您需要 N 个对象的 M 个向量,如果您选择第一种情况,将分配多少次共享内存?是 M,但是如果我们想创建包含 MxN 个对象的向量,为什么需要分配 M 次内存呢?

所以这里正确的实现是默认指向静态内存,只有在对象改变时才分配内存。在这种情况下,分配 N 个对象的 M 个向量将为您提供... 1 个“共享”内存分配。

在您的情况下,您违反了正确的语义滥用复制构造函数,即: 1)不明显 2) 不是最优的 现在你必须还清。

【讨论】:

-1。您可能完全不同意海报的设计问题,但问题中的程序在旧标准和新标准中都是有效的并且格式正确。问题是在两种标准下它是否是同一个程序,而不是设计决策。

以上是关于std::vector、默认构造、C++11 和重大更改的主要内容,如果未能解决你的问题,请参考以下文章

函数参数默认值 std:vector 初始化与 Rcpp 和 C++11?

c++11 使用 std::map 作为返回值

C++ 11 std::vector push_back 方法多次调用 copy/dest?

具有成员 std::vector 的移动语义

使用 std::vector 时如何将索引信息传递给元素构造函数?

内联使用成员对象的非默认显式构造函数