2013-04-12 2 views
5

Diciamo ho un tipo che non è né mobile né copiabile:Come inizializzare una sequenza di oggetti non mobili e non copiabili?

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

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

Ora dato un numero noto al momento della compilazione (chiamare N), c'è un modo che possa creare una "sequenza" di questi in pila con ognuno inizializzato con i numeri da 0 a N-1? Sarei soddisfatto con un array in stile C foo[N], uno std::array< foo, N > o forse anche un std::tuple di qualche tipo.

Quello che sto cercando di evitare sta scrivendo out:

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

quando ci si sente come questo è qualcosa che il compilatore dovrebbe essere in grado di fare per me. Il meglio che sono riuscito a fare è usare boost::optional.

Ma questo si basa sulla logica di runtime anche se tutte le informazioni richieste sono disponibili in fase di compilazione. Inoltre, mi rimane qualcosa che si comporta come una serie di indicatori.

risposta

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

Se questo sembra un sacco di problemi, lo è. Ma potresti facilmente racchiudere quella funzionalità in una classe.

1

Anche se non è strettamente una matrice, è possibile ordinare di raggiungere questo obiettivo con il modello di ricorsione

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

Usage:

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

come la risposta da Benjamin Lindley, ma confezionato in una classe:

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

Ora le cose sono un po 'più semplici:

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

Ho paura di dover votare. Il problema qui è che se 'construct' genera un'eccezione, chiamerai il distruttore di un oggetto che non è mai stato costruito. –

+0

@DavidStone Sì, questo è un problema. Il costruttore non dovrebbe buttare. Ho aggiunto un 'static_assert'. Nella risposta di Benjamin Lindley, quando il costruttore fallisce, non viene chiamato nessun distruttore. – R1tschY

+0

modificato a upvote –