PDA

Visualizza la versione completa : [C++] Puntatori a strutture dinamiche


RooccoXXI
02-02-2011, 17:30
Ciao a tutti.

Sto tentando di creare una classe Vector per rappresentare dei vettori euclidei. Per creare dei vettori a qualsiasi dimensione e di qualsiasi tipo ho deciso di utilizzare dei template e utilizzare un puntatore per tenere traccia degli array o dei vector che contengono le coodrinate (così l'utente ha piena libertà sulla dimensione dei vettori).

Ora, mentre con gli array non vedo problemi perché sono strutture statiche, mi chiedo se ha senso utilizzate un puntatore a uno std::vector, che può essere riallocato se vengono aggiunti nuovi dati.


// Vector class

#ifndef VECTOR_H
#define VECTOR_H

#include <vector>
using std::vector;

template <typename T>
class Vector
{
public:
Vector();
Vector(const T [], int);
Vector(const vector<T>);
~Vector();

private:
T* vector_Ptr;
int vector_size;
};

#endif


template <typename T>
Vector<T>::Vector(const vector<T> array)
{
vector_Ptr = &vector<T>
vector_size = array.size();
}

Inoltre mi chiedevo se è possibile limitare l'istanza di una classe template a tipi fondamentali... Sto vaneggiando?! :)

shodan
02-02-2011, 18:38
Originariamente inviato da RooccoXXI
Ciao a tutti.
Ora, mentre con gli array non vedo problemi perché sono strutture statiche, mi chiedo se ha senso utilizzate un puntatore a uno std::vector, che può essere riallocato se vengono aggiunti nuovi dati.

Concettualmente non fai altro che creare un T** visto che std::vector di suo ha un T* interno. Quindi potrebbe non avere senso.
Ha più senso invece utilizzare un puntatore T* interno alla tua classe da manipolare come ti serve.



Inoltre mi chiedevo se è possibile limitare l'istanza di una classe template a tipi fondamentali

Dipende dal compilatore che usi. Se conforme allo standard C++0x ( g++ > 4.3, Vc++2010), si puo fare una cosa del genere.


template <typename T>
class Vector {
static_assert( std::is_fundamental<T>::value, "Il tipo di dato non è nativo");
etc...

Altrimenti sei costretto a specializzare ogni istanza per i tipi fondamentali.


// Vector class
#ifndef VECTOR_H
#define VECTOR_H

#include <vector>
using std::vector;

template <typename T>
class Vector
{
public:
Vector();
Vector(const T [], int);
Vector(const vector[/QUOTE]&<T> );
Vector& operator=(const Vector& )

// eventuale supporto per la move semantics (solo C++0x)
Vector& operator=(Vector&& )
Vector& operator=(Vector&& )

~Vector();

private:
};

#endif

template <typename T>
Vector<T>::Vector(const vector<T>& array)
{
...
}

RooccoXXI
02-02-2011, 21:15
Originariamente inviato da shodan
Concettualmente non fai altro che creare un T** visto che std::vector di suo ha un T* interno. Quindi potrebbe non avere senso.
Ha più senso invece utilizzare un puntatore T* interno alla tua classe da manipolare come ti serve.


Immaginavo che std::vector avesse un puntatore interno (ovviamente!). Però allora se il mio puntatore è inutile (tra l'altro dovrebbe essere vector<T>* e non solo T*, giusto?) come faccio a creare una classe con un solo dato membro (il mio Vettore vero e proprio) che funzioni sia per gli std::vector che per gli array?!



Dipende dal compilatore che usi. Se conforme allo standard C++0x ( g++ > 4.3, Vc++2010), si puo fare una cosa del genere.


template <typename T>
class Vector {
static_assert( std::is_fundamental<T>::value, "Il tipo di dato non è nativo");
etc...

Altrimenti sei costretto a specializzare ogni istanza per i tipi fondamentali.


Non ho più aggiornato il compilatore. :incupito: Sono fermo alla 4.2.1. Prossimamente però l'aggiorno ma prima di quest'estate non avrò tempo di aggiornare più di quel tanto le mie conoscenze in C++ (C++0x)!

Il passaggio per riferimento l'avevo già visto, comunque grazie! :)

shodan
02-02-2011, 21:47
Intanto devi avere due dati: T* e una variabile per la dimensione (int, std::size_t; long long, fai tu).

Poi basta fare l'overload del costruttore.
Ad esempio (i tipi delle variabili le semplifico in int per comodità).


// costruttori.
Vector(T* p, int size) // costruttore che prende il puntatore al primo elemento e la dimensione.
{
_Ptr = new T[Size];
std::copy(p, p+size,_Ptr);
}

template <typename InpIter>
Vector(InpIter first, InpIter last) // costruttore che un input iterator al primo e ultimo elemento.
{
_Ptr = new T[ std::distance(first,last) ];
std::copy(first, last,_Ptr);
}

T MyArray0[128];
// etc

Vector vec0(MyArray,128);

T MyArray1[128];
// etc

Vector vec1(MyArray,MyArray+128);
// oppure
// Vector vec1(&MyArray[0],&MyArray[128]);


std::vector std_vec;
// etc
Vector vec1( vec.begin(),vec.end() );


Però se devi usare la tua classe solo per memorizzare dati, stai reinventando la ruota.
Se devi solo travasarli per poi fornire metodi di elaborazione è un altro paio di maniche.

Tra l'altro la domanda iniziale (sul puntatore a vector) è abbastanza strana. Non ti basta un'istanza al vector, senza per forza averne il puntatore?


template <typename T>
class Vector
{
public:
// etc
private:
vector<T> _MyVec;
};

RooccoXXI
02-02-2011, 21:53
Originariamente inviato da shodan
Intanto devi avere due dati: T* e una variabile per la dimensione (int, std::size_t; long long, fai tu).

Poi basta fare l'overload del costruttore.
Ad esempio (i tipi delle variabili le semplifico in int per comodità).


// costruttori.
Vector(T* p, int size) // costruttore che prende il puntatore al primo elemento e la dimensione.
{
_Ptr = new T[Size];
std::copy(p, p+size,_Ptr);
}

template <typename InpIter>
Vector(InpIter first, InpIter last) // costruttore che un input iterator al primo e ultimo elemento.
{
_Ptr = new T[ std::distance(first,last) ];
std::copy(first, last,_Ptr);
}

T MyArray0[128];
// etc

Vector vec0(MyArray,128);

T MyArray1[128];
// etc

Vector vec1(MyArray,MyArray+128);
// oppure
// Vector vec1(&MyArray[0],&MyArray[128]);


std::vector std_vec;
// etc
Vector vec1( vec.begin(),vec.end() );


Però se devi usare la tua classe solo per memorizzare dati, stai reinventando la ruota.
Se devi solo travasarli per poi fornire metodi di elaborazione è un altro paio di maniche.

Tra l'altro la domanda iniziale (sul puntatore a vector) è abbastanza strana. Non ti basta un'istanza al vector, senza per forza averne il puntatore?


template <typename T>
class Vector
{
public:
// etc
private:
vector<T> _MyVec;
};


Ovvio che devo avere anche il dato della dimensione. Comunque non devo reinventare la ruota (anche se sono convinto che sia molto utile per capire veramente come funzionano le cose!), ma voglio creare una classe completa, con ridefinizione degli operatori e tutto per utilizzare dei vettori (intesi come oggetto matematico): somma, moltiplicazione per un numero, prodotto scalare, prodotto vettoriale, ...!

E da questa classe derivare poi una classe Point. :)

Comunque tu consigli allora di utilizzare un vector all'interno della mia classe ed eventualmente se l'utente decide di utilizzare gli array, creare il costruttore in modo che prenda l'array e lo copi nel mio vector?! (Ho capito correttamente ciò che intendevi?!)

shodan
02-02-2011, 22:16
Originariamente inviato da RooccoXXI
(anche se sono convinto che sia molto utile per capire veramente come funzionano le cose!)

Questo senz'altro. Anche perché poi ti trovi di fronte a problematiche che magari non avevi neanche previsto.



Comunque tu consigli allora di utilizzare un vector all'interno della mia classe ed eventualmente se l'utente decide di utilizzare gli array, creare il costruttore in modo che prenda l'array e lo copi nel mio vector?! (Ho capito correttamente ciò che intendevi?!)

Si. In sostanza la tua classe diventa un wrapper per un vector.
Alla fine devi solo creare i costruttori sul modello del vector.
In più non ti devi preoccupare di implementare il costruttore di copia, l'operatore di assegnamento, distruttore e il passaggio a C++0x "potrebbe" risultare indolore.
In pratica devi solo concentrarti sulle funzioni che intendi sviluppare.
Riprendendo l'esempio di prima:


#include <vector>
using std::vector;

template <typename T>
class Vector
{
public:
Vector();
Vector(const T* ptr, int dim) : _MyVec(ptr, ptr+dim) {}

template <typename InpIter>
Vector(InpIter a, InpIter b) : _MyVec(a,b) {}

// funzioni specializzate.

private:
vector<T> _MyVec;
};

Tra l'altro ti consiglierei di usare gli iteratori, non l'operatore[] del vector. In questo modo se un giorno vuoi passare da un vector a una deque o list, basta cambiare una linea di codice.

RooccoXXI
02-02-2011, 22:23
Originariamente inviato da shodan
Questo senz'altro. Anche perché poi ti trovi di fronte a problematiche che magari non avevi neanche previsto.



Si. In sostanza la tua classe diventa un wrapper per un vector.
Alla fine devi solo creare i costruttori sul modello del vector.
In più non ti devi preoccupare di implementare il costruttore di copia, l'operatore di assegnamento, distruttore e il passaggio a C++0x "potrebbe" risultare indolore.
In pratica devi solo concentrarti sulle funzioni che intendi sviluppare.
Riprendendo l'esempio di prima:


#include <vector>
using std::vector;

template <typename T>
class Vector
{
public:
Vector();
Vector(const T* ptr, int dim) : _MyVec(ptr, ptr+dim) {}

template <typename InpIter>
Vector(InpIter a, InpIter b) : _MyVec(a,b) {}

// funzioni specializzate.

private:
vector<T> _MyVec;
};

Tra l'altro ti consiglierei di usare gli iteratori, non l'operatore[] del vector. In questo modo se un giorno vuoi passare da un vector a una deque o list, basta cambiare una linea di codice.

Con gli iteratori faccio ancora un po' di fatica, ma appunto per questo pensavo di utilizzarli! :).

Comunque con l'array devo per forza passare la dimensione? Ero convinto di si ma su questo sito sembrerebbe non obbligatorio.
C++ Reference (http://www.cplusplus.com/reference/stl/vector/vector/)

Infatti utilizza l'operatore sizeof() per ricavare la dimensione dell'array. Questo funziona si a se l'array è passato per riferimento sia se è passato per riferimento di tipo puntatore?

shodan
02-02-2011, 22:35
No. Funziona solo se l'array è definito così:


T my_array[128]; // sizeof(my_array) == 128 * sizeof(T)

Ma se il tuo array è allocato dinamicamente, o decade in un puntatore perché passato a una funzione:


T* my_array = new T[128]; // sizeof(my_array) == sizeof(void*)

il giochetto non funziona più.

RooccoXXI
02-02-2011, 22:41
Originariamente inviato da shodan
No. Funziona solo se l'array è definito così:


T my_array[128]; // sizeof(my_array) == 128 * sizeof(T)

Ma se il tuo array è allocato dinamicamente, o decade in un puntatore perché passato a una funzione:


T* my_array = new T[128]; // sizeof(my_array) == sizeof(void*)

il giochetto non funziona più.

Che significa "decade in un puntatore"?!

shodan
02-02-2011, 23:02
Array C e puntatori hanno legami stretti.
Quando passi un array a una funzione, di fatto passi un puntatore che ha lo stesso nome dell'array.



void funzione(int* p) {
// p == arr
}
int arr[128];

funzione(arr);

questo si definisce "decadimento" dell'array in puntatore.
La notazione:


void funzione(int[] p) {
// p == arr
}

è solo un alias della prima versione.

Tuttavia mentre di un puntatore puoi farne quel che vuoi, con l'array non puoi.



void funzione(int* p) {
// p == arr
p++; // ok, punta ad arr[1];
}
int arr[128];

funzione(arr);
// arr++ errore di compilazione!
int* a = arr;
a++ // ok, a punta ad arr[1]. Altro decadimento di un array C in un puntatore.

In pratica il nome dell'array C è un puntatore const che consente di modificare il contenuto, ma non la locazione a cui punta. Per farlo devi effettuare una indirezione tramite un altro puntatore.

Detto più terra terra, a livello implementativo gli array di per se non esistono: sono puntatori speciali gestiti dal compilatore.

Loading