22

我正在为 linux 和 windows 编写一个应用程序,并注意到 GCC 构建对复制构造函数产生了很多无用的调用。

这是产生此行为的示例代码:

struct A
{
    A()                { std::cout << "default" << std::endl; }
    A(A&& rvalue)      { std::cout << "move"    << std::endl; }
    A(const A& lvalue) { std::cout << "copy"    << std::endl; }
    A& operator =(A a) { std::cout << "assign"  << std::endl; return *this; }
};

BOOST_AUTO_TEST_CASE(test_copy_semantics)
{
    std::vector<A> vec_a( 3 );
}

此测试仅创建一个包含 3 个元素的向量。我期望 3 个默认构造函数调用和 0 个副本,因为没有A左值。

在 Visual C++ 2010 中,输出为:

default
move
default
move
default
move

在 GCC 4.4.0 (MinGW) 中,(-O2 -std=c++0x),输出为:

default
copy
copy
copy

发生了什么事,我该如何解决?实际课程的副本很昂贵,默认构造和移动很便宜。

4

5 回答 5

18

两种实现(Visual C++ 2010 和 GCC 4.4.0)都有错误。正确的输出是:

default
default
default

这在 23.3.5.1 [vector.cons]/4 中指定:

要求:T 应为 DefaultConstructible。

不允许该实现假定 A 是 MoveConstructible 或 CopyConstructible。

于 2011-02-01T17:18:03.003 回答
6

看起来问题是您拥有的 g++ 版本没有完全兼容 C++0x 的库。特别是,在 C++03 中,std::vector 的 size 构造函数具有以下签名:

// C++ 03
explicit vector(size_type n, const T& value = T(),
const Allocator& = Allocator());

使用该函数签名和您的调用,将创建一个临时对象,然后由常量引用绑定,并为每个元素创建它的副本。

而在 C++0x 中有不同的构造函数:

// C++0x
explicit vector(size_type n);
vector(size_type n, const T& value, const Allocator& = Allocator());

在这种情况下,您的调用将匹配第一个签名,并且元素应该默认构造为在容器上放置 new (正如@Howard Hinnant 在他的回答中正确指出的那样,编译器根本不应该调用移动构造函数)。

您可以尝试检查最新版本的 g++ 是否具有更新的标准库,或者您可以通过手动添加元素来解决此问题:

std::vector<A> v;
v.reserve( 3 );     // avoid multiple relocations
while (v.size() < 3 ) v.push_back( A() );
于 2011-02-01T17:18:34.090 回答
1

那么试试这个:

std::vector<A> vec_a;
vec_a.reserve(3);
for (size_t i = 0; i < 3; ++i)
  vec_a.push_back(A());

您要做的是强制初始化过程对每个值使用构造+移动,而不是构造,然后复制/复制/复制。这些只是不同的哲学。库的作者不可能知道对于任何给定类型来说哪个是最好的。

于 2011-02-01T17:13:49.560 回答
1

在将默认构造对象复制到“this”对象时,您可以添加特殊(廉价)案例来复制 ctor 算法。这只是一种解决方法,但是,这种行为很奇怪。两个编译器(库)都在堆栈上创建一个临时对象,然后 gcc 将此临时对象复制到目标 3 次;msvc 重新创建临时对象 3 次 (!)(也在堆栈上)并将 3 次移动到目标。我不明白为什么他们不直接在原地创建对象。

于 2011-02-01T18:15:00.490 回答
0

我认为,所有 3 个变体都不违反 C++0x 草案。它需要以下内容: 1. 用 n 个值初始化元素构造一个向量 2. T 应为 DefaultConstructible 3. 在 n 中线性

所有 3 个变体都满足 1,因为默认 + 复制、默认 + 移动等价于默认所有 3 个变体都满足 3 所有 3 个变体都满足 2:它们适用于 DefaultConstructible 类型。特定算法可用于可移动类型。在 STL 中,对具有不同功能的类型使用不同版本的算法是一种普遍做法。

于 2011-02-08T15:52:58.057 回答