标准库容器在 GCC 中的右值上生成大量副本

发布于 2024-10-15 11:07:17 字数 880 浏览 3 评论 0 原文

我正在为 Linux 和 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

发生了什么情况以及如何修复它?对于实际职业来说,副本很昂贵,默认构建和移动很便宜。

I'm writing a app for both linux & windows, and noticed that the GCC build is producing a lot of useless calls to the copy constructor.

Here's an example code to produce this behavior:

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 );
}

This test just creates a vector of 3 elements. I expect 3 default constructor calls and 0 copies as there are no A lvalues.

In Visual C++ 2010, the output is:

default
move
default
move
default
move

In GCC 4.4.0 (MinGW), (-O2 -std=c++0x), the output is:

default
copy
copy
copy

What is going on and how do I fix it? Copies are expensive for the actual class, default construction and moves are cheap.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

风吹雪碎 2024-10-22 11:07:17

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

default
default
default

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

要求:T 应是 DefaultConstructible。

不允许实现假设 A 是可移动构造的或可复制构造的。

Both implementations (Visual C++ 2010 and GCC 4.4.0) are in error. The correct output is:

default
default
default

This is specified in 23.3.5.1 [vector.cons]/4:

Requires: T shall be DefaultConstructible.

The implementation is not allowed to assume that A is either MoveConstructible nor CopyConstructible.

夏了南城 2024-10-22 11:07:17

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

// 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() );

Looks like the problem is that the version of g++ that you have does not have a C++0x fully compliant library. In particular, in C++03, the size constructor of std::vector has the following signature:

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

With that function signature and your call, a temporary is created, then bound by the constant reference and copies of it are created for each one of the elements.

while in C++0x there are different constructors:

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

In this case, your call will match the first signature, and the elements should be default constructed with placement new over the container (as @Howard Hinnant correctly points out in his answer the compiler should not call the move constructor at all).

You can try and check if more recent versions of g++ have an updated standard library, or you can work around the issue by manually adding the elements:

std::vector<A> v;
v.reserve( 3 );     // avoid multiple relocations
while (v.size() < 3 ) v.push_back( A() );
半岛未凉 2024-10-22 11:07:17

然后试试这个:

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

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

Try this then:

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

What you're trying to do is force the initialization process to use construct+move for each value instead of construct and then copy/copy/copy. These are just different philosophies; the libraries authors couldn't possibly know which is going to be the best for any given type.

逆夏时光 2024-10-22 11:07:17

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

You can add special (cheap) case to copy ctor algorithm when copying default constructed object to "this" object. It's just a workaround, however, the behaviour is strange enough.
Both compilers (libraries) create a temporary object on the stack, then gcc copies this temporary to the targets 3 times; msvc recreates temporary object 3 times (!) (on the stack too) and moves 3 timesn to targets. I don't understand why they don't create objects directly in place.

ゃ人海孤独症 2024-10-22 11:07:17

我认为,所有 3 个变体都不违反 C++0x 草案。它需要以下内容:
1. 构造一个具有 n 个值初始化元素的向量
2. T 应是默认可构造的
3. Linear in n

所有 3 个变体都满足 1,因为 default + copy、default + move 等价于 default
所有 3 个变体都满足 3
所有 3 个变体都满足 2:它们适用于 DefaultConstructible 类型。特定算法可用于可移动类型。对于具有不同功能的类型使用不同版本的算法是 STL 的一般做法。

I think, that all 3 variants do not violate C++0x draft. It requires following:
1. Constructs a vector with n value-initialized elements
2. T shall be DefaultConstructible
3. Linear in n

All 3 variants satisfy 1, as default + copy, default + move are equivalent to default
All 3 variants satisfy 3
All 3 variants satisfy 2: they work for DefaultConstructible types. Specific algorithm can be used for Moveable types. It is a general practice in STL to use different versions of algorithms for types with different capabilities.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文