2013-04-12 4 views
5

Lassen Sie uns sagen, dass ich eine Art haben, die weder beweglich noch kopierbar ist:Wie initialisiert man eine Sequenz von nicht beweglichen, nicht kopierbaren Objekten?

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

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

nun eine Zahl zum Zeitpunkt der Kompilierung bekannt gegeben (nennen wir es N), ist es eine Möglichkeit, dass ich eine „Sequenz“ von diesen erstellen auf dem Stapel mit jeder mit den Zahlen 0 bis N-1 initialisiert? Ich würde mit einem C-artigen Array foo[N], einem std::array< foo, N > oder vielleicht sogar einem std::tuple irgendeiner Art zufrieden sein.

Was ich versuche zu vermeiden, das Schreiben aus:

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

wenn es so fühlt sich etwas ist der Compiler sollte für mich tun können. Das Beste, was ich mir vorstellen konnte, ist boost::optional.

Aber das beruht auf Laufzeitlogik, obwohl alle erforderlichen Informationen zur Kompilierzeit verfügbar sind. Außerdem habe ich etwas, das sich wie eine Reihe von Zeigern verhält.

Antwort

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

Wenn das wie eine Menge Ärger scheint, ist es. Aber Sie könnten diese Funktionalität problemlos in einer Klasse kapseln.

1

Obwohl nicht unbedingt ein Array ist, können Sie sortieren diese Rekursion mit Vorlage erreichen

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

Verbrauch:

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

Wie die Antwort von Benjamin Lindley, aber in einer Klasse verpackt:

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

Jetzt sind die Dinge ein bisschen einfacher:

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

Ich fürchte, ich muss dies abstimmen. Das Problem hierbei ist, dass, wenn 'construct' eine Ausnahme auslöst, Sie den Destruktor eines Objekts aufrufen werden, das nie konstruiert wurde. –

+0

@DavidStone Ja, das ist ein Problem. Der Konstruktor sollte nicht werfen. Ich habe eine 'static_assert' hinzugefügt. In der Antwort von Benjamin Lindley, wenn der Konstruktor fehlschlägt, wird kein Destruktor aufgerufen. – R1tschY

+0

Geändert zu einem upvote –