2013-04-12 3 views
5

Powiedzmy mam typ, który nie jest ani przemieszczać ani copyable:Jak zainicjować ciąg nieruchomych, niekopiowalnych obiektów?

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

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

Teraz podany numer znany w czasie kompilacji (nazywają to N), czy jest jakiś sposób, że mogę stworzyć „sekwencji” tych na stosie, z każdym zainicjowanym numerami od 0 do N-1? Byłbym zadowolony z tablicy w stylu C: foo[N], std::array< foo, N > lub nawet std::tuple.

Co staram się unikać pisze się:

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

kiedy wydaje się, jakby to coś kompilator powinien być w stanie zrobić dla mnie. Najlepsze, co udało mi się wymyślić, to: boost::optional.

Ale to opiera się na logice środowiska wykonawczego, mimo że wszystkie wymagane informacje są dostępne podczas kompilacji. Poza tym pozostaje mi coś, co zachowuje się jak tablica wskaźników.

Odpowiedz

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(); 

Jeśli to wydaje się dużo kłopotów, to jest. Ale można łatwo zamknąć tę funkcję w klasie.

1

Chociaż nie jest ściśle tablicą, można rodzaju osiągnięcia tego z szablonu rekursji

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; 
    } 
}; 

Zastosowanie:

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

Podobnie jak odpowiedź od Benjamina Lindley, ale zapakowane w klasie:

#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; 
}; 

Teraz sprawy są nieco łatwiejsze:

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

Obawiam się, że muszę to głosować. Problem polega na tym, że jeśli "konstrukt" zgłasza wyjątek, wywołasz destruktor obiektu, który nigdy nie został skonstruowany. –

+0

@DavidStone Tak, to jest problem. Konstruktor nie powinien rzucać. Dodałem 'static_assert'. W odpowiedzi od Benjamina Lindleya, gdy konstruktor zawiedzie, nie jest wywoływany destruktor. – R1tschY

+0

Zmieniono na lotto –

Powiązane problemy