2010-09-10 10 views
6

Questo è un sequel di un related post che ha chiesto l'eterna domanda:Posso avere contenitori polimorfici con la semantica del valore in C++ 11?

Posso avere contenitori polimorfi con semantica di valore in C++?

La domanda è stata posta in modo leggermente errato. Sarebbe dovuto essere più simile a:

Posso avere contenitori STL di un tipo di base memorizzato in base al valore in cui gli elementi mostrano un comportamento polimorfico?

Se si pone la domanda in termini di C++, la risposta è "no". Ad un certo punto, triterai gli oggetti memorizzati in base al valore.

Ora ripeto la domanda, ma rigorosamente in termini di C++ 11. Con le modifiche al linguaggio e alle librerie standard, è ora possibile memorizzare oggetti polimorfici in base al valore in un contenitore STL?

Sono ben consapevole della possibilità di memorizzare un puntatore intelligente per la classe base nel contenitore - questo non è quello che sto cercando per, come sto cercando di costruire oggetti nello stack senza utilizzando new.

Considerate se si vuole (dal post linked) come base, C++ esempio:

#include <iostream> 

using namespace std; 

class Parent 
{ 
    public: 
     Parent() : parent_mem(1) {} 
     virtual void write() { cout << "Parent: " << parent_mem << endl; } 
     int parent_mem; 
}; 

class Child : public Parent 
{ 
    public: 
     Child() : child_mem(2) { parent_mem = 2; } 
     void write() { cout << "Child: " << parent_mem << ", " << child_mem << endl; } 

     int child_mem; 
}; 

int main(int, char**) 
{ 
    // I can have a polymorphic container with pointer semantics 
    vector<Parent*> pointerVec; 

    pointerVec.push_back(new Parent()); 
    pointerVec.push_back(new Child()); 

    pointerVec[0]->write();  
    pointerVec[1]->write();  

    // Output: 
    // 
    // Parent: 1 
    // Child: 2, 2 

    // But I can't do it with value semantics 

    vector<Parent> valueVec; 

    valueVec.push_back(Parent()); 
    valueVec.push_back(Child());  // gets turned into a Parent object :(

    valueVec[0].write();   
    valueVec[1].write();   

    // Output: 
    // 
    // Parent: 1 
    // Parent: 2 

} 
+2

boost: ptr_vector dovrebbe fare ciò che vuoi. –

+1

Si noti che è possibile simulare la semantica del valore (almeno in termini di contenitori STL) con qualcosa come "boost :: ptr_container'. –

+0

@ Martin: come osi battermi per 15 secondi?!? –

risposta

4

Solo per divertimento, basandomi sul commento di James su un sistema basato su modelli, ho realizzato questa implementazione simile a Vector. Mancano molte funzionalità e potrebbero essere bacati, ma è un inizio!

#include <iostream> 
#include <vector> 
#include <boost/shared_ptr.hpp> 

template <typename T> 
class Vector 
{ 
public: 
    T &operator[] (int i) const { return p[i]->get(); } 
    template <typename D> 
    void push_back(D &x) { p.push_back(ptr_t(new DerivedNode<D>(x))); } 

private: 
    class Node 
    { 
    public: 
     virtual T &get() = 0; 
    }; 

    template <typename D> 
    class DerivedNode : public Node 
    { 
    public: 
     DerivedNode(D &x) : x(x) {} 
     virtual D &get() { return x; } 
    private: 
     D x; 
    }; 

    typedef boost::shared_ptr<Node> ptr_t; 
    std::vector<ptr_t> p; 
}; 

/////////////////////////////////////// 

class Parent 
{ 
    public: 
     Parent() : parent_mem(1) {} 
     virtual void write() const { std::cout << "Parent: " << parent_mem << std::endl; } 
     int parent_mem; 
}; 

class Child : public Parent 
{ 
    public: 
     Child() : child_mem(2) { parent_mem = 2; } 
     void write() const { std::cout << "Child: " << parent_mem << ", " << child_mem << std::endl; } 

     int child_mem; 
}; 


int main() 
{ 
    Vector<Parent> v; 

    v.push_back(Parent()); 
    v.push_back(Child()); 

    v[0].write(); 
    v[1].write(); 
} 
+0

Questo è vicino al '' Poly di Adobe Source Libraries ' 'classi. Bello! – fbrereto

+2

stai reinventando la ruota: 'boost :: ptr_vector' fa meglio (non è coinvolto' shared_ptr'). Si noti inoltre che il requisito di base non era "nuovo". Con un 'new' è banale (e ci sono le librerie). –

8

Non si può certo avere una matrice polimorfico (o vector). Il requisito che gli elementi di una matrice siano memorizzati in modo contiguo nella memoria è fondamentalmente incompatibile con il fatto che diversi tipi di classi derivate possono avere dimensioni diverse.

Nessuno dei contenitori di libreria standard consente di archiviare oggetti di tipi di classi derivate differenti in un singolo contenitore.

+0

Anche con ad es. 'lista', il problema delle dimensioni esiste ancora: quanto dovrebbe essere grande un nodo? –

+1

@Oli: Beh, il mio primo pensiero è che potresti avere un modello di classe nodo derivato che può essere istanziato con oggetti di dimensioni diverse, il che richiederebbe almeno (a) un modo standard per determinare la dimensione del tipo dinamico di un oggetto e (b) un modo standard per clonare un oggetto. Anche allora, penso che ci sarebbero problemi nel far funzionare correttamente il dispatch dinamico. Non credo che un contenitore del genere funzionerebbe meglio di un contenitore di puntatori. Nel migliore dei casi sarebbe disordinato; potrebbe non essere affatto possibile. Sarebbe comunque un progetto divertente con cui giocare. –

+0

vedi la mia risposta! Ho preso la tua idea e corro con essa ... i commenti sono benvenuti –

2

Prima di tutto, le vostre esigenze non sono ancora perfettamente chiare. Presumo che tu voglia "memoria in linea" per il contenitore; quindi, ad esempio, in un valore "polimorfo" vector, tutti gli elementi sarebbero adiacenti in memoria (con solo il riempimento intermedio necessario per l'allineamento corretto).

Ora, è possibile se si è disposti a fornire un elenco completo di tutti i tipi che si inseriranno nel contenitore in fase di compilazione. L'implementazione più semplice sarebbe quella di utilizzare un'unione di tutti i tipi possibili come tipo del backing array, che garantirebbe dimensioni e allineamento appropriati, e lo stesso O (1) accesso per indice, al costo di uno spazio sprecato sugli elementi di tipi più piccoli. Posso entrare in questo con più dettagli se vuoi.

Se l'elenco dei tipi è ora noto in anticipo o se non si desidera tale tipo di overhead, è necessario mantenere un indice separato di puntatori (o offset dall'inizio del backing store) a elementi, in modo da poter eseguire l'accesso O (1). Inoltre, dati i problemi di allineamento, non sono sicuro di poterlo fare anche in C++ 03 completamente portatile, sebbene sia possibile farlo in C++ 0x.

+0

Se i tuoi tipi hanno copia-costruttori, ecc., Non puoi metterli in un sindacato. –

+2

È vero, anche se puoi sempre usare lo stesso trucco che usa "boost :: variant" (o, per dirla tutta, usalo direttamente). –

+1

+1: 'std :: vector >' è la migliore risposta che potrei fare anche io. Si noti che i visitatori di questa struttura devono solo 'return_type operator() (Base const &)' poiché gli altri sono convertibili in 'Base'. –

Problemi correlati