2013-04-12 3 views
5

Скажем, у меня есть тип, который не является ни движимое, ни копируемыми:Как инициализировать последовательность недвигаемых, не скопируемых объектов?

struct foo 
{ 
    explicit foo(size_t){} 
    ~foo(){} 

    foo(foo const &) = delete; 
    foo(foo &&) = delete; 
    foo& operator=(foo const &) = delete; 
    foo& operator=(foo &) = delete; 
}; 

Теперь данное число, известное во время компиляции (назовем его N), есть ли способ, что я могу создать «последовательность» из них в стеке, каждый из которых инициализируется цифрами от 0 до N-1? Я был бы доволен C-образным массивом foo[N], std::array< foo, N > или, возможно, даже std::tuple.

То, что я пытаюсь избежать в выписывая:

foo f0(0), f1(1), ... fNminus1(N-1); 

, когда он чувствует, что это то, что компилятор должен быть в состоянии сделать для меня. Лучшее, что я смог придумать, это использовать boost::optional.

Но это зависит от логики выполнения, хотя вся необходимая информация доступна во время компиляции. Кроме того, у меня осталось что-то, что ведет себя как массив указателей.

ответ

3
// create a type with the proper alignment 
typedef std::aligned_storage<sizeof(foo), std::alignment_of<foo>::value>::type buffer_type; 

const int N = 10; 
// create an array of uninitialized raw data 
buffer_type storage_buffer[N]; 

// initialize each foo object with placement new 
for (size_t i=0; i<N; ++i) 
    new (storage_buffer + i) foo(i); 

foo * fp = (foo*)(&storage_buffer); 
// access your foo objects via fp 


// you must manually call the destructor of each object 
for (size_t i=0; i<N; ++i) 
    fp[i].~foo(); 

Если это похоже на много хлопот, это так. Но вы можете легко инкапсулировать эту функциональность в классе.

1

Хотя не строго массив, вы можете сортировать сделать это с шаблоном рекурсии

template< typename T, size_t N > 
struct type_array : public type_array< T, N-1 > { 
    // this is the Nth element 
    T elem; 
    // it is constructed with N 
    type_array() : elem(N) {} 

    // member function to return the Nth element 
    T & get(size_t n) { 
     if (n == N) { 
      return elem; 
     } else { 
      return type_array< T, N-1 >::get(n); 
     } 
    } 
}; 

// base case when N == 0 
template< typename T > 
struct type_array<T, 0> { 
    T elem; 
    type_array() : elem(0) {} 
    T & get(size_t n) { 
     return elem; 
    } 
}; 

Использование:

type_array< foo, 100 > foo_array; // construct 100 foos 
foo_array.get(1);     // foo with n == 1 
foo_array.get(2);     // foo with n == 2 
1

Как ответ от Benjamin Линдли, но упакована в классе:

#include <type_traits> 
#include <utility> 
#include <new> 

template<typename T> 
class uninitialized { 
public: 
    constexpr uninitialized() { } 

    ~uninitialized() { 
    get().~T(); 
    } 

    explicit uninitialized(const uninitialized& other) { 
    construct(other); 
    } 

    explicit uninitialized(uninitialized&& other) { 
    construct(std::move(other)); 
    } 

    template<class... Args> 
    explicit uninitialized(Args&&... args) { 
    construct(std::forward<Args>(args)...); 
    } 

    template<class... Args> 
    void construct(Args&&... args) noexcept { 
    static_assert(std::is_nothrow_constructible<T, Args...>::value, "constructor should not throw!"); 
    ::new(getPointer()) T (std::forward<Args>(args)...); 
    } 

    uninitialized& operator = (const T& t) { 
    get() = t; 
    return *this; 
    } 

    uninitialized& operator = (T&& t) { 
    get() = std::move(t); 
    return *this; 
    } 

    T* operator ->() { return getPointer(); }  
    T& operator *() { return get(); }  
    T* operator &() { return getPointer(); }  
    T* getPointer() { return reinterpret_cast<T*>(&data); }  
    T& get() { return *reinterpret_cast<T*>(&data); } 

    const T* operator ->() const { return getPointer(); }  
    const T& operator *() const { return get(); }  
    const T* operator &() const { return getPointer(); }  
    const T* getPointer() const { return reinterpret_cast<const T*>(&data); }  
    const T& get() const { return *reinterpret_cast<const T*>(&data); } 

private: 
    std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type data; 
}; 

Теперь все немного проще:

uninitialized<foo> f[N]; 

for (size_t i = 0; i < N; ++i) 
    f[i].construct(i); 

for (const auto& fooref : f) 
    fooref->bar(); 

// foo::~foo is called for you 
+0

Я боюсь, что я должен проголосовать за это. Проблема здесь в том, что если 'construct' генерирует исключение, вы вызовете деструктор объекта, который никогда не был создан. –

+0

@DavidStone Да, это проблема. Конструктор не должен бросать. Я добавил 'static_assert'. В ответе Бенджамина Линдли, когда конструктор терпит неудачу, деструктор не вызван. – R1tschY

+0

Изменено на верхний уровень –