[libcxx-dev] std::vector using copy constructor on existing elements during reserve()

Richard Smith via libcxx-dev libcxx-dev at lists.llvm.org
Mon Apr 29 01:54:51 PDT 2019


On Sun, 28 Apr 2019 at 18:36, Eric Fiselier <eric at efcs.ca> wrote:
> Changing the behavior of unary noexcept  is a much larger and complicated change than fixing vector.
>
> At the moment `-fno-exceptions` is a subset of standard C++. Changing noexcept would make it non-standard and non-portable.
> Do we want that?

If move_if_noexcept or vector::resize switches to a more efficient
algorithm under -fno-exceptions, that creates the same kind of
surprises that we'd see if functions became noexcept(true) by default
under -fno-exceptions; either way, code does not have portable
behavior or performance between implementations / implementation
flags, so I think it's a difference in degree rather than in kind.
(People will write code that relies on their copy constructor not
being called when the vector resizes, and would be surprised if they
get different behavior and performance when (for instance) they turn
on -fexceptions so they can use a third-party library that requires
it.)

So I think the concerns here really apply to all of our options,
although to differing extents.

If you want to modify libc++'s vector and friends to
move-even-if-not-noexcept when exceptions are disabled, that's up to
you and your fellow libc++ maintainers; I agree that it appears to be
a conforming implementation strategy, but it's a complex decision due
to the above concerns.

> And there's just nastiness like this:
>
> void bar();
> void bar() noexcept(noexcept(bar())); // Cool
> void bar(); noexcept(false); // Also Cool
>
> We should pursue std::vector separately. I think the current wording allows either moves or copies, as long as "if an exception is thrown, there are no effects".
> For example [1]
>
> void push_back(const T& x); void push_back(T&& x);
> Remarks: [...] If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T or by any InputIterator operation there are no effects. If an exception is thrown while inserting a single element at the end and T is Cpp17CopyInsertable or is_­nothrow_­move_­constructible_­v<T> is true, there are no effects. Otherwise, if an exception is thrown by the move constructor of a non-Cpp17CopyInsertable T, the effects are unspecified.
>
> Cpp17CopyInsertible implies Cpp17MoveInsertable. And we meet the "if exception, no effects" requirements trivially.
> I might be squinting at the wording too hard, but I think I can fix this as QoI.
>
> /Eric
>
> [1] http://eel.is/c++draft/container.requirements#general-11
> [2] http://eel.is/c++draft/sequences#vector.modifiers
>
> On Wed, Apr 17, 2019 at 7:44 PM Duncan Exon Smith via libcxx-dev <libcxx-dev at lists.llvm.org> wrote:
>>
>>
>>
>> On 2019 Apr  17, at 11:06, Louis Dionne <ldionne at apple.com> wrote:
>>
>>
>>
>> On Apr 17, 2019, at 13:38, JF Bastien <jfbastien at apple.com> wrote:
>>
>>
>>
>> On Apr 17, 2019, at 10:12 AM, Louis Dionne via libcxx-dev <libcxx-dev at lists.llvm.org> wrote:
>>
>>
>>
>> On Apr 17, 2019, at 12:39, Duncan P. N. Exon Smith <dexonsmith at apple.com> wrote:
>>
>> I'm curious about how people feel this should/could behave with -fno-exceptions.  IIRC, Libc++ calls the copy constructor (following the standard), but -fno-exceptions isn't really standard, and from a user perspective that doesn't make sense.  `noexcept` is essentially meaningless with -fno-exceptions.
>>
>>
>> I personally feel like it would be surprising if -fno-exceptions caused the move constructor to be called instead of the copy constructor. My mental model is that -fno-exceptions only disables exceptions as a language feature, but doesn't really instruct libraries to provide a different API (even though the difference in this case is very small). Instead, I feel like it would make more sense if "everything" was implicitly marked noexcept when -fno-exceptions is used (because that's really the case). The library would behave optimally with -fno-exceptions without having to do anything special.
>>
>>
>> IIUC what you’re saying: the library should just use the noexcept operator to do its business, and it seem like -fno-exceptions should make noexcept always return true? That would be pretty consistent (more than teaching the library about -fno-exceptions for move / copy).
>>
>>
>> Yes, that's what I mean. I agree with Eric that moving instead of copying provides value, but I think the right way to do it is not to special case libc++, but instead to have all code bases become better under -fno-exceptions by making noexcept do the right thing.
>>
>>
>> Yeah, that makes sense to me too.
>>
>> +Richard, I could have sworn you and I chatted quickly about this back in 2016 by my mail-search-fu is failing me.  What are your thoughts on having -fno-exceptions affect the value of `noexcept`?
>>
>>
>>
>> Louis
>>
>>
>> Thoughts?
>>
>> On Apr 16, 2019, at 23:19, Eric Fiselier via libcxx-dev <libcxx-dev at lists.llvm.org> wrote:
>>
>>
>>
>> On Tue, Apr 16, 2019 at 2:25 PM Michael Narigon via libcxx-dev <libcxx-dev at lists.llvm.org> wrote:
>>>
>>> std:vector is using the element copy constructor on existing elements during its resizing operation in reserve(). I would expect it to use the move constructor for efficiency. Is this intended behavior?
>>
>>
>> Yes, this behavior is mandated by the standard. The copy constructor is used when the move constructor is not noexcept. From cppreference's `std::move_if_noexcept` documentation:
>>
>>> std::vector::resize, which may have to allocate new storage and then move or copy elements from old storage to new storage.
>>> If an exception occurs during this operation, std::vector::resize undoes everything it did to this point, which is only possible if
>>> std::move_if_noexcept was used to decide whether to use move construction or copy construction. (unless copy constructor is
>>>
>>> not available, in which case move constructor is used either way and the strong exception guarantee may be waived)
>>
>>  https://en.cppreference.com/w/cpp/utility/move_if_noexcept
>>
>> If you add `noexcept` to your move constructor, vector should call the move constructor.
>>
>>
>>
>>>
>>>
>>> This is with the libc++ delivered with Xcode 10.1.
>>>
>>> Attached is a sample program displaying the behavior.
>>> _______________________________________________
>>> libcxx-dev mailing list
>>> libcxx-dev at lists.llvm.org
>>> https://lists.llvm.org/cgi-bin/mailman/listinfo/libcxx-dev
>>
>> _______________________________________________
>> libcxx-dev mailing list
>> libcxx-dev at lists.llvm.org
>> https://lists.llvm.org/cgi-bin/mailman/listinfo/libcxx-dev
>>
>>
>>
>> _______________________________________________
>> libcxx-dev mailing list
>> libcxx-dev at lists.llvm.org
>> https://lists.llvm.org/cgi-bin/mailman/listinfo/libcxx-dev
>>
>>
>> _______________________________________________
>> libcxx-dev mailing list
>> libcxx-dev at lists.llvm.org
>> https://lists.llvm.org/cgi-bin/mailman/listinfo/libcxx-dev


More information about the libcxx-dev mailing list