is uninitialized_copy/fill(In first, In last, For

2019-07-02 08:05发布

问题:

I like to know how things work and as such have been delving deep into the c++ standard library. Something occurred to me the other day.

It is required that containters (for example: std::vector<int, std::allocator<int> >) use the allocator specified for allocations. Specifically the standard says:

23.1.8

Copy constructors for all container types defined in this clause copy an allocator argument from their respective first parameters. All other constructors for these container types take an Allocator& argument (20.1.5), an allocator whose value type is the same as the container’s value type. A copy of this argument is used for any memory allocation performed, by these constructors and by all member functions, during the lifetime of each container object. In all container types defined in this clause, the member get_allocator() returns a copy of the Allocator object used to construct the container.

Also later in the standard it says (in a few different spots but i'll pick one) things like this:

explicit deque(size_type n, const T& value = T(), const Allocator& = Allocator());

Effects: Constructs a deque with n copies of value, using the specified allocator.

OK, so on to my question.

Let's take std::vector as an example, the natural and efficient way to implement something like:

vector<T, A>::vector(const vector& x)

might look something like this:

template <class T, class A>
vector<T, A>::vector(const vector& x)  {
    pointer p = alloc_.allocate(x.size());
    std::uninitialized_copy(x.begin(), x.end(), p);
    first_ = p;
    last_  = p + x.size();
    end_   = p + x.size();
}

specifically, we allocate some memory and then copy construct all the members in place. Not bothering to do something like new value_type[x.size()] because that would default construct the array only to overwrite it!.

but, this doesn't use the allocator to do the copy construction...

I could manually write a loop which does something like this:

while(first != last) {
    alloc_.construct(&*dest++, *first++);
}

but that's a waste, it's nearly identical to std::uninitialized_copy, the only difference is that is uses the allocator instead of placement new.

So, would you consider it an oversight that the standard doesn't have the (seemingly obvious to me) set of functions like these:

template <class In, class For, class A>
For uninitialized_copy(In first, In last, For dest, A &a);

template <class In, class Size, class For, class A>
For uninitialized_copy_n(In first, Size count, For dest, A &a);

template <class For, class T, class A>
void uninitialized_fill(For first, For last, const T& x, A &a);

template <class For, class Size, class T, class A>
void uninitialized_fill_n(For first, Size count, const T& x, A &a);

I would imagine that these types of functions (even though they are trivial to implement manually... until you try to make them exception safe) would prove fairly useful if people want to implement there own containers and such and make efficient use of copy construction while using allocators.

Thoughts?

回答1:

I'm not sure whether we could call it an "oversight", per se.

No, you can't provide your own allocator to these specialised algorithms. But then there are other things that the standard doesn't contain, either.

@MarkB identifies a very good reason that the standard shouldn't do this (that the range has no knowledge of the container's allocator). I'd go so far as to say it's just an inherent limitation.

You can always re-invent uninitialized_copy for your needs, knowing what the allocator should be. It's just a two-line for loop.



回答2:

If these functions were free-functions, I can't see any way that the compiler could detect allocator mismatches since the allocator type isn't retained by the iterator. This in turn could result in a variety of hard-to-find problems.