在写this问题的答案时,我遇到了一个有趣的情况 - 问题演示了一个人想要将一个类放在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错误?
答案 0 :(得分:6)
Visual C ++ 2012无法auto-generate the move constructor and the move assignment operator。只会修复in the upcoming 2015 version的缺陷。
您可以通过向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's answer详细解释的那样,标准实际上并不需要移动赋值运算符。 vector<T>::resize()
的相关概念是MoveInsertable和DefaultInsertable,只需使用移动构造函数,您的初始实现就可以满足这些概念。
VC的实现还要求移动分配这一事实是不同的缺陷,已在VS2013中修复。
答案 1 :(得分:5)
最重要的是,由于c ++ 11,std::vector<>
可以存储不可复制的类型。 (example)让我们来看看cppreference。
直到c ++ 11,如你所知,T应该是可复制的。
T必须符合CopyAssignable和CopyConstructible的要求。
然而,在c ++ 11中,要求完全改变了。
对元素施加的要求取决于对容器执行的实际操作。通常,要求元素类型是完整类型并满足可擦除的要求,但许多成员函数强加了更严格的要求。
.. Erasable是:
如果给定
,则类型T可以从容器X中删除
A
分配器类型定义为X::allocator_type
获取的
m
从A
X::get_allocator()
类型的左值
p
由容器准备的T*
类型的指针以下表达式格式正确:
std::allocator_traits<A>::destroy(m, p);
查看&#34;类型要求&#34; std::vector::resize() reference:
T必须符合MoveInsertable和DefaultInsertable的要求才能使用重载(1)。
所以T不需要是可复制的 - 它只需要可销毁,可移动和默认的构造。
此外,由于c ++ 14,删除了完整类型的限制。
对元素施加的要求取决于对容器执行的实际操作。通常,要求元素类型满足Erasable的要求,但许多成员函数强加了更严格的要求。 如果分配器满足分配器完整性要求,则可以使用不完整的元素类型实例化此容器(但不是其成员)。
因此,我认为这是因为VS2012的标准符合性差。它在最新的C ++上有一些缺陷(例如noexcept
)
C ++ 11标准论文N3337说
void resize(size_type sz);
效果:如果
sz <= size()
,相当于erase(begin() + sz, end());
。如果size() < sz
,请附加sz - size()
将值初始化的元素添加到序列中。要求:T应为CopyInsertable到* this。
因此,在严格的c ++ 11中,在这种情况下你不能使用std::vector::resize()
。 (你可以使用std::vector
然而,it is a standard defect并在C ++ 14中修复。我想很多编译器都能很好地处理不可复制的类型,因为复制并不需要实现std::vector::resize()
。虽然VS2012不起作用,但是因为VS2012的另一个错误是@ComicSansMS回答,而不是因为std::vector::resize()
本身。
答案 2 :(得分:3)
VS2012是一个带有一些C ++ 11特性的C ++编译器。将它称为C ++ 11编译器有点紧张。
它的标准库非常C ++ 03。它对移动语义的支持很少。
通过VS2015,编译器仍然是带有一些C ++ 11特性的C ++ 11,但它对移动语义的支持要好得多。
VS2015仍然缺乏完整的C ++ 11 constexpr
支持,并且SFINAE支持不完整(他们称之为#34;表达SFINAE&#34;)以及一些连锁库故障。它还具有非静态数据成员初始值设定项,初始化程序列表,属性,通用字符名称,某些并发细节以及其预处理程序不兼容的缺陷。 This is extracted from their own blog
与此同时,现代gcc和clang编译器已经完成了对C ++ 14的支持并且拥有广泛的C ++ 1z支持。 VS2015具有有限的C ++ 14功能支持。几乎所有的C ++ 1z支持都在实验分支中(这是公平的)。
所有3个编译器都在它们支持的功能之上存在错误。
您在这里遇到的是您的编译器不是完整的C ++ 11编译器,因此您的代码不起作用。
在这种情况下,C ++ 11标准也存在缺陷。缺陷报告通常由编译器修复并折叠成&#34; C ++ 11编译模式&#34;通过编译器,以及纳入下一个标准。有问题的缺陷是显而易见的,基本上每个实际执行C ++ 11标准的人都忽略了缺陷。
C ++标准要求某些可观察的行为。这些任务通常会将编译器编写者限制在某些狭窄的实现空间(具有微小的变化),假设具有相当好的实现质量。
与此同时,C ++标准留下了很多自由。 C ++向量中的迭代器类型可以是标准下的原始指针,也可以是引用计数智能索引器,如果使用不当或其他原因产生额外错误。编译器可以使用这种自由来使他们的调试版本具有额外的错误检查(捕获程序员的未定义行为),或者使用该自由来尝试可以授予额外性能的不同技巧(在分配时存储其大小和容量的向量)缓冲区可能更小,以便存储,通常当您要求大小/容量时,无论如何都会很快访问数据。)
这些限制通常围绕数据生命周期和复杂性限制。
通常会编写一些参考实现,分析其局限性和复杂性界限,并将其作为限制提出。有时候部件会被放置,而且会更松散。而不是参考实现所需要的,这为编译器或库编写者提供了自由。
作为一个例子,有人抱怨C ++ 11中的无序地图类型受到标准的过度约束,并阻止了可以实现更高效实施的创新。如果放在所述容器上的约束较少,不同的供应商可以进行试验,并且可能会聚合更快的容器而不是当前的设计。
缺点是标准库的修改很容易破坏二进制兼容性,因此如果稍后添加的约束排除了某些实现,编译器编写者和用户可能会非常恼火。
答案 3 :(得分:1)
C ++标准规定了T
对几乎所有库容器函数的约束。
例如,在草案n4296中,[vector.capacity] / 13中定义的T
std::vector::resize
的约束是。
Requires: T shall be MoveInsertable and DefaultInsertable into *this.
我无法访问各种版本的C ++的最终标准进行比较,但我认为VS 2012在此示例中的C ++ 11支持中不符合。