54

I ran today against a quite subtle issue I'd like to have your opinion on.

Consider the following garden-variety shared-body-idiom class:

struct S
{
    S() : p_impl(new impl) {}
private:
    struct impl;
    boost::shared_ptr<impl> p_impl;
};

The fun appears when you try to put those into vectors in the following way:

std::vector<S> v(42);

Now, with MSVC 8 at least, all the elements in v share the same impl member. Actually, what causes this is the vector constructor:

template <typename T, typename A = ...>
class vector
{
    vector(size_t n, const T& x = T(), const A& a = A());
    ...
};

Under the scenes, only one S object gets default constructed, the n elements of the vector are copied from it.

Now, with C++11, there are rvalue references. So it cannot work like this. If a vector is constructed as

std::vector<S> v(42);

then most likely, implementations will chose to default construct the n objects inside the vector, since copy construction may not be available. This would be a breaking change in this case.

My question is:

  1. Does the C++03 standard mandates that std::vector must have a constructor defined as above, ie. with a default argument ? In particular is there a guarantee that the entries of the vector object get copied instead of default constructed ?
  2. What does the C++11 standard say about this same point ?
  3. I see this as a possibility for a breaking change between C++03 and C+11. Has this issue been investigated ? Solved ?

PS: Please no comments about the default constructor of the class S above. It was this or implementing some form of lazy construction.

Xeo
  • 129,499
  • 52
  • 291
  • 397
Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • 2
    This is just plain shocking. Compiling C++ code but giving it a new meaning in C++0X is the worst possible scenario I can think to. I'm very tempted to just pass on this hand like I did with DOS 4 or Windows ME. – 6502 Apr 22 '11 at 20:12
  • 3
    Is that supposed to be a threat? Are you comparing C++0x to Windows ME? – jalf Apr 23 '11 at 18:09
  • 5
    All due respect, it's a little funny hearing that from a guy named "6502"… – Potatoswatter Apr 27 '11 at 01:12

2 Answers2

46

Does the C++03 standard mandate that std::vector must have a constructor defined as above, i.e. with a default argument? In particular is there a guarantee that the entries of the vector object get copied instead of default constructed?

Yes, the specified behavior is that x is copied n times so that the container is initialized to contain with n elements that are all copies of x.


What does the C++11 Standard say about this same point?

In C++11 this constructor has been turned into two constructors.

vector(size_type n, const T& x, const Allocator& = Allocator()); // (1)
explicit vector(size_type n);                                    // (2)

Except for the fact that it no longer has a default argument for the second parameter, (1) works the same way as it does in C++03: x is copied n times.

In lieu of the default argument for x, (2) has been added. This constructor value-initializes n elements in the container. No copies are made.

If you require the old behavior, you can ensure that (1) is called by providing a second argument to the constructor invocation:

std::vector<S> v(42, S());

I see this as a possibility for a breaking change between C++03 and C++11. I see this as a possibility for a breaking change between C++03 and C++11. Has this issue been investigated? Solved?

Yes, as your example demonstrates, this is indeed a breaking change.

As I am not a member of the C++ standardization committee (and I haven't paid particularly close attention to library-related papers in the mailings), I don't know to what degree this breaking change was discussed.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • So my code will behave differently in C++0X then ? I thought they were going for backward compatibility ? – Alexandre C. Apr 22 '11 at 19:36
  • Yes, I realize I can provide a default value, but this still means that my code will get compiled to a **different** program if we decide to use a C++0x-enabled compiler. – Alexandre C. Apr 22 '11 at 19:41
  • 1
    @Alexandre: Yes, it will behave differently with a C++0x Standard Library implementation. I'm not on the standardization committee, though, so I can't say for certain whether the scenario you describe was discussed. – James McNellis Apr 22 '11 at 19:41
  • Hard to believe... this is a huge change in semantic... is this a deliberate decision or just an oversight? Did they change also when assignments/copy-constructors are used in algorithms? ... IMO this would just break everything. BTW g++ 4.4.5 still uses copy construction even with `--std=c++0x`. – 6502 Apr 22 '11 at 19:43
  • 1
    @6502: The change was a deliberate decision not an oversight (I think it would be difficult to oversightedly add constructor overloads :-P). I'm not quite sure what you are asking in "did they also change when assignments..." Some things have changed due to the introduction of the move ctor and move op= to the language, but I'm not sure how that would specifically affects the algorithms. (Also, I am not 100% familiar with what the final decision was to the "Implicit move must go" debate at the end of last year...). – James McNellis Apr 22 '11 at 19:48
  • 1
    For what it's worth, ignoring the backwards compatibility issue for a moment, the new specification is better and it should have been specified that way from the beginning (or rather, if value initialization had existed in its current form in 1998, then it should have been specified this way from the beginning). I certainly understand your concerns about backwards compatibility though. – James McNellis Apr 22 '11 at 19:50
  • @James McNellis: Wow... so C++ code will compile quietly in C++0X but with different behavior. I'd say this is the worst possible scenario (especially in an highly complex language like C++). With algorithms I meant for example what is going to happen when I do `v.erase(v.begin())` in an `std::vector`? Will assignment be used? destructor followed by placement-new copy-construction? other stuff?... I find this anyway quite incompatible with Stroustrup description (Maintain stability and compatibility -- don't break old code, and if you absolutely must, don't break it quietly). – 6502 Apr 22 '11 at 20:02
  • @James: Actually I realized this after one long hour wondering why my results were all merged... so I wholeheartedly agree with you, but either the language is backward compatible, or it is not. – Alexandre C. Apr 22 '11 at 20:02
  • 9
    @Alexandre: Then it is not. There are a couple of other cases where they break backwards compatibility. They just try not to do it when it can be avoided, or when the payoff isn't worth it. But absolute 100% backwards compatibility hasn't been on the table for years. – jalf Apr 23 '11 at 18:08
  • Certainly since they started adding keywords, if not before. – Steve Jessop Jun 19 '11 at 00:23
  • 1
    @Steve: Adding keywords does not generally break things _silently_. I agree with @6502 et. al. that this a highly questionable change. – Nemo Jun 25 '11 at 01:56
  • 1
    There are a *lot* of questions on S.O. asking why the copy-construction was taking place though... usually surfacing as "why is my vector so much slower than an array?". The old behaviour makes sense for C.O.W. / reference counted objects, but the new one is more intuitive in general. – Tony Delroy Jun 27 '11 at 08:38
  • 3
    @Nemo: Sure, I'm responding to Alexandre C, "either the language is backward compatible, or it is not". It is not, and blatantly so. If he wants a trichotomy, "either the language is backward compatible; or else it is not; or else every well-formed program in C++03 either maintains in C++0x all behaviour guaranteed by C++03, or is ill-formed in C++0x", then he can use that trichotomy instead of the dichotomy. And it's still "not", just a bit less blatantly, and I agree that it's a pity we can't have the third. – Steve Jessop Jun 27 '11 at 13:41
-3

I think solution for use-case you described is not optimal and not complete, that's why you got problems upgrading to C++11.

C++ always cares about semantic and when you write program in c++ you'd better to understand your semantic. So in your case you wish to create N objects, but while you are not changing them you wish them to share same memory for optimization. Nice idea, but how to get this done: 1) copy constructor. 2) static implementation + copy constructor. Have you considered both solutions?

Consider you need M vectors of N objects, how many times shared memory will be allocated if you choose 1st scenario? It is M, but why do we need to allocate memory M times if we want to create vectors containing MxN objects?

So correct implementation here is to point to static memory by default, and allocate memory only if object is changed. In such a case allocating M vectors of N objects will give you... 1 'shared' memory allocation.

In your case you violated correct semantic abusing copy constructor, which is: 1) not obvious 2) not optimal and now you have to pay off.

  • 9
    -1. You may disagree with the poster's design issues all you want, but the program in the question is valid and well formed in both the old and the new standard. The question is about it being or not being the same program under both standards, not about design decisions. – André Caron Mar 01 '12 at 23:27