开发者

Standard library containers producing a lot of copies on rvalues in GCC

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 开发者_Go百科<< "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.


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.


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


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜