视觉 C++标准是否指定编译器的 STL 实现详细信息
visual Does the C++ standard specify STL implementation details for the compiler?
在写这个问题的答案时,我遇到了一个有趣的情况 - 这个问题演示了一个场景,即人们想要将类放在 STL 容器中,但由于缺少复制构造函数/移动构造函数/赋值运算符而未能这样做。在这种特殊情况下,错误由 std::vector::resize
触发。我制作了一个快速片段作为解决方案,并看到了另一个答案,它提供了一个移动构造函数,而不是像我一样的赋值运算符和复制构造函数。另一个答案没有在VS 2012中编译,而clang/gcc对这两种方法都很满意。
第一:
// Clang and gcc are happy with this one, VS 2012 is not
#include <memory>
#include <vector>
class FooImpl {};
class Foo
{
std::unique_ptr<FooImpl> myImpl;
public:
Foo( Foo&& f ) : myImpl( std::move( f.myImpl ) ) {}
Foo(){}
~Foo(){}
};
int main() {
std::vector<Foo> testVec;
testVec.resize(10);
return 0;
}
第二:
// Clang/gcc/VS2012 are all happy with this
#include <memory>
#include <vector>
using namespace std;
class FooImpl {};
class Foo
{
unique_ptr<FooImpl> myImpl;
public:
Foo()
{
}
~Foo()
{
}
Foo(const Foo& foo)
{
// What to do with the pointer?
}
Foo& operator= (const Foo& foo)
{
if (this != &foo)
{
// What to do with the pointer?
}
return *this;
}
};
int main(int argc, char** argv)
{
vector<Foo> testVec;
testVec.resize(10);
return 0;
}
为了了解发生了什么,我查看了VS 2012中的STL源代码,发现它确实在调用移动赋值运算符,这就是为什么我的示例有效的原因(我没有Linux机器可以访问来了解clang/gcc中发生的事情),而另一个没有,因为它只有移动复制构造函数。
因此,这产生了以下问题 - 编译器是否可以自由决定如何实现 STL 方法(在本例中为 std::vector::resize
),因为完全不同的实现可能会导致不可移植的代码?或者这只是一个VS 2012错误?
>Visual C++ 2012 无法自动生成移动构造函数和移动赋值运算符。仅在即将推出的 2015 版本中修复的缺陷。
您可以通过向Foo
添加显式移动赋值运算符来编译您的第一个示例:
#include <memory>
#include <vector>
class FooImpl {};
class Foo
{
std::unique_ptr<FooImpl> myImpl;
public:
Foo( Foo&& f ) : myImpl( std::move( f.myImpl ) ) {}
// this function was missing before:
Foo& operator=( Foo&& f) { myImpl = std::move(f.myImpl); return *this; }
Foo(){}
~Foo(){}
};
int main() {
std::vector<Foo> testVec;
testVec.resize(10);
return 0;
}
正如 ikh 的回答所详细解释的那样,该标准实际上不需要这里的移动分配运算符。vector<T>::resize()
的相关概念是 MoveInsertable 和 DefaultInsertable,只需使用 move 构造函数即可通过初始实现来满足这些概念。
VC的实现也需要移动分配,这是一个不同的缺陷,这在VS2013中已经修复。
感谢ikh和dyp在这件事上做出的有见地的贡献。
最重要的是,从 c++11 开始,std::vector<>
可以存储不可复制的类型。(示例)让我们来看看 cpp 偏好。
在 c++11 之前,如您所知,T 应该是可复制的。
T 必须满足 CopyAssignable 和 CopyConstructable 的要求。
但是,在 c++11 中,要求完全改变了。
对元素施加的要求取决于对容器执行的实际操作。通常要求元素类型为完整类型,满足 Erasable 的要求,但许多成员函数提出了更严格的要求。
..可擦除是:
类型 T 可从容器 X 中擦除,如果给定
A
定义为X::allocator_type
的分配器类型
m
从X::get_allocator()
获得的A
类型的左值
p
容器准备的T*
类型的指针以下表达式格式正确:
std::allocator_traits<A>::destroy(m, p);
看看 std::vector::resize() 引用的"类型要求":
T 必须满足 MoveInsertable 和 DefaultInsertable 的要求才能使用重载 (1)。
所以 T 不需要是可复制的——它只需要可销毁、可移动和默认的可构造。
此外,从 c++14 开始,完全类型的限制被删除。
对元素施加的要求取决于对容器执行的实际操作。通常,要求元素类型满足可擦除的要求,但许多成员函数提出了更严格的要求。如果分配器满足分配器完整性要求,则可以使用不完整元素类型实例化此容器(但不是其成员)。
因此,我认为这是因为VS2012的标准一致性差。它在最新的C++上有一些缺陷(例如 noexcept
)
C++11标准纸N3337说
空隙调整大小(size_type sz);
效果:若
sz <= size()
,相当于erase(begin() + sz, end());
。如果size() < sz
,则追加sz - size()
序列中的值初始化元素。要求:T 应可复制插入到 *this 中。
因此,在严格的 c++11 中,在这种情况下不能使用std::vector::resize()
。(不过你可以使用std::vector
)
但是,这是一个标准缺陷,并在 C++14 中修复。 而且我想许多编译器都可以很好地处理不可复制的类型,因为确实不需要复制来实现std::vector::resize()
。虽然VS2012不起作用,但这是因为VS2012的另一个错误@ComicSansMS回答,而不是因为std::vector::resize()
本身。
VS2012是一个C++编译器,具有一些C++11功能。 称其为 C++11 编译器有点牵强。
它的标准库非常C++03。 它对移动语义的支持很少。
到VS2015,编译器仍然是具有一些C++11功能的C++11,但它对移动语义的支持要好得多。
VS2015仍然缺乏完整的C++11 constexpr
支持,并且具有不完整的SFINAE支持(他们称之为"表达式SFINAE")和一些连锁库故障。 它还在非静态数据成员初始值设定项、初始值设定项列表、属性、通用字符名称、某些并发详细信息方面存在缺陷,并且其预处理器不合规。 这是从他们自己的博客中提取的。
同时,现代 gcc 和 clang 编译器已经完成了 C++14 支持,并具有广泛的 C++1z 支持。 VS2015 具有有限的 C++14 功能支持。 几乎所有的C++1z支持都在实验分支中(这是公平的)。
所有 3 个编译器在它们支持的功能之上都有错误。
您在这里遇到的是,您的编译器不是完整的 C++11 编译器,因此您的代码不起作用。
在这种情况下,C++11标准也存在缺陷。 缺陷报告通常由编译器修复,并由编译器折叠成"C++11编译模式",并被纳入下一个标准。 所讨论的缺陷非常明显,基本上每个实际实施C++11标准的人都忽略了这个缺陷。
C++标准规定了某些可观察的行为。 通常,这些任务将编译器编写者限制在某些狭窄的实现空间(有微小的变化),假设实现质量不错。
同时,C++标准留下了很大的自由。 C++向量的迭代器类型可以是标准下的原始指针,也可以是引用计数智能索引器,在使用不当时会产生额外的错误,或者完全是其他东西。 编译器可以使用这种自由来通过额外的错误检查(为程序员捕获未定义的行为)来检测他们的调试版本,或者利用这种自由来尝试可以授予额外性能的不同技巧(将其大小和容量存储在分配的缓冲区中的向量可能更小,通常当您请求大小/容量时,无论如何您很快就会访问数据)。
限制通常与数据生存期和复杂性边界有关。
通常编写一些参考实现,分析其局限性和复杂性边界,并将其作为限制提出。 有时,部分比参考实现所需的"松散",这为编译器或库编写者提供了自由。
例如,有人抱怨 C++11 中的无序映射类型受到标准的过度约束,并阻止了可以允许更高效实现的创新。 如果对上述容器施加的约束较少,则可以尝试不同的供应商,并且可能会收敛更快的容器而不是当前的设计。
缺点是对标准库的修订很容易破坏二进制兼容性,因此如果以后添加的约束排除了一些实现,编译器编写者和用户可能会非常恼火。
C++ 标准规定了几乎所有库容器函数的T
约束。
例如,在草案 n4296 中,[vector.capacity]/13 中定义的std::vector::resize
的T
约束是。
Requires: T shall be MoveInsertable and DefaultInsertable into *this.
我无法访问各种版本的C++的最终标准进行比较,但我假设VS 2012在此示例中的C++11支持不符合。
- 为什么使用 NDK 不能存在不同的 stl 实现?
- glibcxx STL 在实现 std::valarray::sum() 时是否不正确?
- 如何使用STL实现Prim算法?
- 有没有办法让 C 预处理器更改我正在使用的 STL 实现?
- C 11 std ::在不同的STL实现中使用哪些算法
- 使用C 中的STL实现DF时遇到的分割故障
- 为什么STL实现不使用断言来检测未定义的行为
- 支持C++11的开源STL实现
- C++对原始STL实现中"construct"方法逻辑的理解
- C++11 及更高版本中的替代 STL 实现
- 修复使用 OpenCV 的混合 STL 实现
- 为什么我们需要交换在反向STL实现?加速c++(第8.4节)
- BFS的STL实现:访问邻接列表时出现问题
- 在STL实现中有中心分配队列或向量吗?
- 在c++中使用STL实现BFS
- 如果一个Android NDK应用程序加载了多个共享的c++ STL实现,它的行为是什么?
- STL实现的MVP设计模式
- 用STL c++实现非递增列表
- 如何使用STL实现自定义字符串类?
- 视觉 C++标准是否指定编译器的 STL 实现详细信息