22

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.

Inverse
  • 4,408
  • 2
  • 26
  • 35
  • 1
    Have you looked at the `` header? What is the constructor doing? The GCC behavior is consistent with the C++03 spec, where an object is default constructed then copied N times. It's possible that your version of the standard library doesn't support the new constructor added to C++0x, which default constructs N elements. – James McNellis Feb 01 '11 at 17:13
  • 1
    Because the example uses C++0x language features, I'm assuming this is a question about the C++0x spec, not the C++03 spec. – Howard Hinnant Feb 01 '11 at 17:20
  • 1
    Same output with GCC 4.5 – tstenner Feb 01 '11 at 18:18
  • What options (flags) did you use to compile the g++ version. – Martin York Feb 01 '11 at 18:35

5 Answers5

18

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.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • You're right, my next question was going to be why is VC move constructing at all. Is there anything that can be done? Any idea if stlport or ea's implementations are more conforming? – Inverse Feb 01 '11 at 18:10
  • This std::lib: http://libcxx.llvm.org/ gets the right answer, but hasn't been ported to Windows. There are parties interested in porting it to Windows and who could no doubt use help. – Howard Hinnant Feb 01 '11 at 18:18
  • 4
    I've reported the Visual C++ issue. – James McNellis Feb 01 '11 at 18:25
  • 1
    @James if it's actually a std library issue not a compiler issue you should probably report it to [Dinkumware](http://www.dinkumware.com/) too – Rup Feb 01 '11 at 19:10
  • 1
    Just to follow up: this has been fixed in Visual C++. If you compile the code with the Visual Studio 11 Developer Preview, it will produce the correct output. – James McNellis Oct 31 '11 at 17:11
6

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() );
Community
  • 1
  • 1
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
1

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.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
1

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.

user396672
  • 3,106
  • 1
  • 21
  • 31
0

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.

Konstantin Tenzin
  • 12,398
  • 3
  • 22
  • 20