简体   繁体   中英

Emplacement-like construction for std::vector

Imagine I want to construct a fixed-size std::vector of objects without move or copy constructors, such as std::atomic<int> . In this case the underlying std::atomic class has a 1-arg constructor which takes an int , as well as a default constructor (which initializes the value to 0).

Using the initializer_list syntax like std::vector<std::atomic<int>> v{1,2,3} doesn't work, because the arguments are first converted to the element type T of the vector as part of the creation of the initializer_list and so the copy or move constructor will be invoked.

In the particular case of std::atomic<int> I can default-construct the vector and then mutate the elements after:

std::vector<std::atomic<int>> v(3);
v[0] = 1;
v[1] = 2;
v[2] = 3;

However, in addition to being ugly and inefficient, it isn't a general solution since many objects may not offer post-construction mutation equivalent to what you could get by calling the appropriate constructor.

Is there any way to get the "emplace-like" behavior that I want at vector construction?

A general solution is to make your vector take a custom allocator whose construct method performs the appropriate initialization. In the code below, v uses the MyAllocator<NonMovable> allocator rather than std::allocator<NonMovable> . When the construct method is called with no arguments, it actually calls the constructor with the appropriate argument. In this way, the default constructor can initialize the elements properly.

(For simplicitly, I have made next_value static in this example, but it could just as well be a non-static member variable that's initialized when MyAllocator is constructed.)

#include <stdio.h>
#include <memory>
#include <new>
#include <vector>

struct NonMovable {
    NonMovable(int x) : x(x) {}
    const int x;
};

template <class T>
struct MyAllocator {
    typedef T value_type;
    static int next_value;
    T* allocate(size_t n) {
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    void deallocate(T* p, size_t n) {
        ::operator delete(p);
    }
    template <class U>
    void construct(U* p) {
        new (p) U(++next_value);
    }
};

template <class T> int MyAllocator<T>::next_value = 0;

int main() {
    std::vector<NonMovable, MyAllocator<NonMovable>> v(10);
    for (int i = 0; i < 10; i++) {
        printf("%d\n", v[i].x);
    }
}

http://coliru.stacked-crooked.com/a/1a89fddd325514bf

This is the only possible solution when you're not allowed to touch the NonMovable class and its constructor may require multiple arguments. In the case where you only need to pass one argument to each constructor, there is a much simpler solution that uses the range constructor of std::vector , like so:

std::vector<int> ints(10);
std::iota(ints.begin(), ints.end(), 1);
std::vector<NonMovable> v(ints.begin(), ints.end());

(Though if you can't afford the extra memory, then you'll have to write a custom iterator, which will be a lot more code.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM