OVH Cloud OVH Cloud

dimensions de tableaux

19 réponses
Avatar
Nicolas Bonneel
Bonjour,

Je souhaites realiser la gestion de matrices multidimensionnelles dans mon
programme.
J'ai créé un classe TMatrix, qui comporte le constructeur :

TMatrix::TMatrix(int Shape[], double Valeurs[])
{
dim = sizeof(Shape)/sizeof(Shape[0]);
nbelements = sizeof(Valeurs)/sizeof(Valeurs[0]);

[......]
}

Alors se posent plusieurs questions :
1- Je ne peux pas créer de matrices en faisant directement : TMatrix* mat =
new TMatrix({2,2},{1.1 0.5 0.7 0.6});
, je suis obligé de créer des variables int[] et double[] et les passer
en parametre avec ces valeurs.... Comment puis-je y remedier ?

2- En passant un Shape de : int[] Shape = {2,2}; lorsque j'execute le
programme, il me met dans dim la valeur 1 (et quel que soit le vecteur Shape
d'ailleurs...). Comment cela se fait-il ? Comment y remedier ?!


Merci beaucoup !

Nicolas Bonneel
http://www.byosphere.com

10 réponses

1 2
Avatar
drkm
"Nicolas Bonneel" writes:

TMatrix::TMatrix(int Shape[], double Valeurs[])
{
dim = sizeof(Shape)/sizeof(Shape[0]);
nbelements = sizeof(Valeurs)/sizeof(Valeurs[0]);


Hou, ... Tu vas au devant d'ennuis. Ne serait-ce pas plus simple
d'utiliser des std::vector<> ?

TMatrix::TMatrix( vector< int > shapes , vector< double > values )
{
dim = shapes.size() ;
nbelements = values.size() ;

Ou, si dim et nbelements sont des membres :

TMatrix::TMatrix( vector< int > shapes , vector< double > values )
: dim( shapes.size() )
, nbelements( values.size() )
{

Tu peux aussi utiliser deux paires d'itérateurs, si tu ne veux pas
te cantoner à un conteneur particulier. Tu pourras alors même
utiliser des tableaux C en arguments. Mais il faut alors ici faire un
constructeur template.

Si le but est de te familiariser avec C++, la solution utilisant
std::vector<> est de loin la plus simple, ÀMHA.

--drkm

Avatar
kanze
Nicolas Bonneel wrote:

Je souhaites realiser la gestion de matrices
multidimensionnelles dans mon programme. J'ai créé un classe
TMatrix, qui comporte le constructeur :

TMatrix::TMatrix(int Shape[], double Valeurs[])


C-à-d :
TMatrix::TMatrix( int* shape, double* valeurs ) ;

{
dim = sizeof(Shape)/sizeof(Shape[0]);


Sur ma machine, en mode 32 bits, ça donnerait toujours 1, et en
mode 64 bits, 2.

nbelements = sizeof(Valeurs)/sizeof(Valeurs[0]);


Sur ma machine, en mode 32 bits, ça donnerait toujours 0, et en
mode 64 bits, 1.

[......]
}


Tu as déjà un problème : si tu te sers des tableaux à la C, il
faut explicitement passer au même temps les longueurs, parce
qu'il n'y a aucun moyen de les récupérer autrement.

Changer la signature du constructeur en :

TMatrix::TMatrix( std::vector< int > const& shape,
std::vector< double > const& valeurs )

résoud cette problème de façon élégante, mais rend l'appel du
constructeur plus pénible (parce que j'imagine qu'assez souvent,
au moins shape est une constante).

Il y a aussi l'altérnatif :

template< size_t N, size_t M >
TMatrix::TMatrix( int const (&shape)[ N ],
double const (&valeurs)[ M ] )

Mais là, tu ne peux l'appeler avec des constantes.

La meilleur solution, c'est sans doute de déclarer :

TMatrix::TMatrix( Shape const& shape, Values const& values )

puis de définir Shape et Values avec une variété de
constructeurs pour permettre tous les cas intéressants. Pour
Shape, je ne vois pas trop de problème :

class Shape
{
public:
template< typename FwdIter >
Shape( FwdIter begin, FwdIter end )
: myData( begin, end )
{
}

template< size_t N >
Shape( int const (&init)[ N ] )
: myData( init, init + N )
{
}
// ...

private:
std::vector< int > myData ;
} ;

(Il y a une solution encore plus élégant, avec un Shape<N> : la
class Shape<N> contient un Shape<N-1> et un int, et il y a
spécialisation pour Shape<1>. Mais j'avoue que l'écrire dépasse
de loin mes compétences en templates.)

En principe, on pouvait se servir de la même technique pour
Values. Seulement, dans le cas de Shape, j'imagine que c'est
plutôt rare que le tableau dépasse une dizaine d'éléments ; en
faire une copie n'est pas la fin du monde. Pour Values, en
revanche, je n'ai pas de mal à imaginer qu'on pourrait avoir des
millions de valeurs, sinon plus. Et même s'il ne faut pas se
précipiter pour s'occuper des problèmes d'optimisation, des
temporaires de cette taille ne sont jamais une bonne chose. Du
coup, il faudrait probablement s'arranger que Values ne copie
pas, mais sait lire les différentes sources, ce qui est plus
difficile. En fin de compte, beaucoup dépend de ce que tu veux
faire avec les valeurs. S'il suffit de pouvoir les copier dans
une collection d'un type connu d'avance, on doit pouvoir se
servir de l'idiome lettre/envélope, avec une fonction virtuelle
copyInto( DestinationType& dest ).

Alors se posent plusieurs questions :
1- Je ne peux pas créer de matrices en faisant directement :
TMatrix* mat =

new TMatrix({2,2},{1.1 0.5 0.7 0.6});
, je suis obligé de créer des variables int[] et double[] et
les passer

en parametre avec ces valeurs.... Comment puis-je y remedier ?


Pratiquement : tu ne peux pas. Il n'y a simplement pas de syntax
dans le langage pour spécifier une liste de valeurs d'une
longueur arbitraire dans une expression.

Pour Shape, je penserais à fournir des constructeurs spéciaux
pour les cas fréquents : Shape( int ), Shape( int, int ), etc.
Jusqu'où tu veux voir dépend de toi, mais j'imagine avec un et
deux int serait un minimum. Et je serais le premier à avouer que
ce n'est pas très élégant de permettre :
TMatrix m( Shape( 5, 10, 3 ), ... )
mais non
TMatrix m( Shape( 5, 10, 3, 2 ), ... )

Une possibilité aussi serait de fornir un constructeur :

Shape::Shape( int first, ... )
: myData( 1, first )
{
va_list a ;
va_start( a, first ) ;
for ( int i = va_arg( a, int ) ;
i > 0 ;
i = va_arg( a, int ) ) {
myData.push_back( i ) ;
}
}

C'est la plus facile à utliser, mais aussi la plus facile pour
faire des connéries. Il exige que la liste des dimensions soit
terminée par une valeur <= 0 ; que l'utilisateur l'oublie, et tu
pars très vite dans les choux.

Et évidemment, tout ça ne vaut que pour Shape. Pour Values, je
ne vois pas où s'arrêter avec les constructeurs surchargés pour
un, deux ... etc. paramètres, et évidemment, il n'y a pas de
valeur sentinelle disponible pour la solution va_args.

2- En passant un Shape de : int[] Shape = {2,2}; lorsque
j'execute le programme, il me met dans dim la valeur 1 (et
quel que soit le vecteur Shape d'ailleurs...). Comment cela se
fait-il ? Comment y remedier ?!


Voir ci-dessus. C'est un problème classique des tableaux de type
C. On ne passe pas des tableaux de type C, on en passe l'adresse
du premier élément. Et même quand on écrit que le programme
prend un tableau comme paramètre, le compilateur le convertit en
pointeur au premier élément. Ce qui veut dire que pour shape,
ton expression vaut « sizeof( int* ) / sizeof( int ) ».

C'est une des raisons (peut-être la raison la plus importante)
pourquoi on dit d'éviter les tableaux de type C, et d'utiliser
vector à sa place. Seulement, passer des valeurs qui sont pour
toi des constantes, comme tu sembles vouloir faire, c'est encore
plus pénible.

N'empeche que c'est avec le constructeur aux deux vecteurs que
je commencerais. Et aussi, les fonctions begin et end classique :

template< typename T, size_t N >
T const*
begin( T const (&array)[ N ] )
{
return array ;
}

template< typename T, size_t N >
T const*
end( T const (&array)[ N ] )
{
return array + N ;
}

et des typedef :
typedef std::vector< int > Shape ;
typedef std::vector< double > Values ;

Ça permettrait quand même à écrire des choses comme :

int const shape[] = { 3, 3 } ;
double const values[] = { 1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0 } ;
TMatrix m( Shape( begin( shape ), end( shape ) ),
Values( begin( values ), end( values ) ) ) ;

Mais je crains que le coût de copier les valeurs soit prohibitif
dans une vraie application.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Laurent Deniau
drkm wrote:
"Nicolas Bonneel" writes:


TMatrix::TMatrix(int Shape[], double Valeurs[])
{
dim = sizeof(Shape)/sizeof(Shape[0]);
nbelements = sizeof(Valeurs)/sizeof(Valeurs[0]);



Hou, ... Tu vas au devant d'ennuis. Ne serait-ce pas plus simple
d'utiliser des std::vector<> ?


Dans ce cas precis, std::valarray<> serait preferable.

TMatrix::TMatrix( vector< int > shapes , vector< double > values )
{
dim = shapes.size() ;
nbelements = values.size() ;

Ou, si dim et nbelements sont des membres :

TMatrix::TMatrix( vector< int > shapes , vector< double > values )
: dim( shapes.size() )
, nbelements( values.size() )
{

Tu peux aussi utiliser deux paires d'itérateurs, si tu ne veux pas
te cantoner à un conteneur particulier. Tu pourras alors même
utiliser des tableaux C en arguments. Mais il faut alors ici faire un
constructeur template.

Si le but est de te familiariser avec C++, la solution utilisant
std::vector<> est de loin la plus simple, ÀMHA.


Si c'est pas le cas, il vaut mieux utiliser une bib existante, parce que
le probleme a l'air simple a premier abord, mais il se complique tres
rapidement.

a+, ld.


Avatar
Nicolas Bonneel
Desolé pour ma réponse tardive mais mon PC est parti en reparation, et
je viens à peine de rentrer chez mes parents pour squatter leur pc ;)

Je souhaites realiser la gestion de matrices
multidimensionnelles dans mon programme. J'ai créé un classe
TMatrix, qui comporte le constructeur :



TMatrix::TMatrix(int Shape[], double Valeurs[])



C-à-d :
TMatrix::TMatrix( int* shape, double* valeurs ) ;


{
dim = sizeof(Shape)/sizeof(Shape[0]);



Sur ma machine, en mode 32 bits, ça donnerait toujours 1, et en
mode 64 bits, 2.


nbelements = sizeof(Valeurs)/sizeof(Valeurs[0]);



Sur ma machine, en mode 32 bits, ça donnerait toujours 0, et en
mode 64 bits, 1.


J'avais pourtant vu cette astuce dans l'aide de c++ builder (à sizeof)
pour connaitre le nombre d'elements d'un tableau...


[......]
}



Tu as déjà un problème : si tu te sers des tableaux à la C, il
faut explicitement passer au même temps les longueurs, parce
qu'il n'y a aucun moyen de les récupérer autrement.

Changer la signature du constructeur en :

TMatrix::TMatrix( std::vector< int > const& shape,
std::vector< double > const& valeurs )

résoud cette problème de façon élégante, mais rend l'appel du
constructeur plus pénible (parce que j'imagine qu'assez souvent,
au moins shape est une constante).


ben en faite je comptais pas laisser shape constant... (comme lorsqu'on
utilise la fonction reshape de matlab...)
enfin disons plutot que le parametre shape est constant tout au long du
constructeur, mais le membre _shape ne l'est pas...



La meilleur solution, c'est sans doute de déclarer :

TMatrix::TMatrix( Shape const& shape, Values const& values )

puis de définir Shape et Values avec une variété de
constructeurs pour permettre tous les cas intéressants. Pour
Shape, je ne vois pas trop de problème :

class Shape
{
public:
template< typename FwdIter >
Shape( FwdIter begin, FwdIter end )
: myData( begin, end )
{
}

template< size_t N >
Shape( int const (&init)[ N ] )
: myData( init, init + N )
{
}
// ...

private:
std::vector< int > myData ;
} ;

(Il y a une solution encore plus élégant, avec un Shape<N> : la
class Shape<N> contient un Shape<N-1> et un int, et il y a
spécialisation pour Shape<1>. Mais j'avoue que l'écrire dépasse
de loin mes compétences en templates.)



Cette solution me parait jolie, permettrait de pouvoir faire des
tableaux d'int, de float et de double ...mais le hic c'est que je ne
connais pas du tout le fonctionnement des templates ;)
Va faloir que je me documente un peu moi! (enfin dans mon cas, Shape
sera toujours un tableau d'entiers, mais values pourront etre des reels
ou des entiers (ou des complexes... ou peut être meme d'autres tableaux
?..))
Bref, tout ca pour dire que j'ai un peu de mal à voir ce que le code
ci-dessus realise exactement ;)


En principe, on pouvait se servir de la même technique pour
Values. Seulement, dans le cas de Shape, j'imagine que c'est
plutôt rare que le tableau dépasse une dizaine d'éléments ; en
faire une copie n'est pas la fin du monde. Pour Values, en
revanche, je n'ai pas de mal à imaginer qu'on pourrait avoir des
millions de valeurs, sinon plus. Et même s'il ne faut pas se
précipiter pour s'occuper des problèmes d'optimisation, des
temporaires de cette taille ne sont jamais une bonne chose. Du
coup, il faudrait probablement s'arranger que Values ne copie
pas, mais sait lire les différentes sources, ce qui est plus
difficile. En fin de compte, beaucoup dépend de ce que tu veux
faire avec les valeurs. S'il suffit de pouvoir les copier dans
une collection d'un type connu d'avance, on doit pouvoir se
servir de l'idiome lettre/envélope, avec une fonction virtuelle
copyInto( DestinationType& dest ).


C'est vrai que values pourra contenir autant d'elements qu'on souhaite
(milliers, millions et plus..?). C'est pour faire une classe matrix
complete où je pourrais deriver une classe vecteur et une classe
matrix2D (pour y faire des factorisations matricielles, des resolutions
etc...)

Alors se posent plusieurs questions :
1- Je ne peux pas créer de matrices en faisant directement :


TMatrix* mat >
new TMatrix({2,2},{1.1 0.5 0.7 0.6});
, je suis obligé de créer des variables int[] et double[] et


les passer

en parametre avec ces valeurs.... Comment puis-je y remedier ?



Pratiquement : tu ne peux pas. Il n'y a simplement pas de syntax
dans le langage pour spécifier une liste de valeurs d'une
longueur arbitraire dans une expression.


ben pourtant on peu declarer : int tableau[] = {1,2,3,4}; non ??


Pour Shape, je penserais à fournir des constructeurs spéciaux
pour les cas fréquents : Shape( int ), Shape( int, int ), etc.


Erf, je vois ce que tu veux dire... mais en fait je comptais laisser
shape sous forme de tableau 1D puisque le tableau que shape décrit peut
contenir autant de dimensions qu'on le souhaite : autant une matrice 4x5
(declarée par un shape(4,5)) qu'une hypermatrice 4x3x2x8x6x5.. declarée
shape(4,3,2,8,6,5).
C'est vrai que d'un maniere générale shape ne contiendra pas beaucoup
plus de 5-6 elements au grand maximum, mais on sait jamais ;)

Jusqu'où tu veux voir dépend de toi, mais j'imagine avec un et
deux int serait un minimum. Et je serais le premier à avouer que
ce n'est pas très élégant de permettre :
TMatrix m( Shape( 5, 10, 3 ), ... )
mais non
TMatrix m( Shape( 5, 10, 3, 2 ), ... )


voilà.. ;)

Une possibilité aussi serait de fornir un constructeur :

Shape::Shape( int first, ... )
: myData( 1, first )
{
va_list a ;
va_start( a, first ) ;
for ( int i = va_arg( a, int ) ;
i > 0 ;
i = va_arg( a, int ) ) {
myData.push_back( i ) ;
}
}

C'est la plus facile à utliser, mais aussi la plus facile pour
faire des connéries. Il exige que la liste des dimensions soit
terminée par une valeur <= 0 ; que l'utilisateur l'oublie, et tu
pars très vite dans les choux.


Là se pose un autre problème : les paramètres proviennent d'un script
interprété. Donc si je connais N paramètres lus du script, en voulant
utiliser le constructeur, je ne saurais pas trop comment faire !
genre un Shape(param[1], param[2],...,param[N]) avec N variable...



Et évidemment, tout ça ne vaut que pour Shape. Pour Values, je
ne vois pas où s'arrêter avec les constructeurs surchargés pour
un, deux ... etc. paramètres, et évidemment, il n'y a pas de
valeur sentinelle disponible pour la solution va_args.


2- En passant un Shape de : int[] Shape = {2,2}; lorsque
j'execute le programme, il me met dans dim la valeur 1 (et
quel que soit le vecteur Shape d'ailleurs...). Comment cela se
fait-il ? Comment y remedier ?!



Voir ci-dessus. C'est un problème classique des tableaux de type
C. On ne passe pas des tableaux de type C, on en passe l'adresse
du premier élément. Et même quand on écrit que le programme
prend un tableau comme paramètre, le compilateur le convertit en
pointeur au premier élément. Ce qui veut dire que pour shape,
ton expression vaut « sizeof( int* ) / sizeof( int ) ».

C'est une des raisons (peut-être la raison la plus importante)
pourquoi on dit d'éviter les tableaux de type C, et d'utiliser
vector à sa place. Seulement, passer des valeurs qui sont pour
toi des constantes, comme tu sembles vouloir faire, c'est encore
plus pénible.


Ben je crois que je vais utiliser le vector moi finallement...
Le fait de passer des vector en parametre du constructeur, ca les passe
par copie ou par reference (comme pour un tableau C) ?
Recopier un vecteur d'1 million d'elements sur un autre, est-ce une
opération longue, ou suffisament bien gérée pour qu'elle soit rapide ?
(disons que l'interet des grosses matrices, c'est de faire des gros
calculs derriere, donc je serai pas à une copie près en terme de cpu...)

N'empeche que c'est avec le constructeur aux deux vecteurs que
je commencerais. Et aussi, les fonctions begin et end classique :

template< typename T, size_t N >
T const*
begin( T const (&array)[ N ] )
{
return array ;
}

template< typename T, size_t N >
T const*
end( T const (&array)[ N ] )
{
return array + N ;
}

et des typedef :
typedef std::vector< int > Shape ;
typedef std::vector< double > Values ;

Ça permettrait quand même à écrire des choses comme :

int const shape[] = { 3, 3 } ;
double const values[] = { 1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0 } ;
TMatrix m( Shape( begin( shape ), end( shape ) ),
Values( begin( values ), end( values ) ) ) ;

Mais je crains que le coût de copier les valeurs soit prohibitif
dans une vraie application.


erf..je sais pas... à toi de me dire ;) lol :)

Enfin merci beaucoup déjà pour toutes ces précisions ! :)

Nicolas Bonneel
http://www.byosphere.com


Avatar
Nicolas Bonneel
Hou, ... Tu vas au devant d'ennuis. Ne serait-ce pas plus simple
d'utiliser des std::vector<> ?



Dans ce cas precis, std::valarray<> serait preferable.


Tient, c'est quoi la difference au fait ?

euh..tient, j'en profite aussi, ca existe pas déjà ca une classe de la
stdlib qui prend en paramètre un Shape et des valeurs pour en faire une
hypermatrice ?...



TMatrix::TMatrix( vector< int > shapes , vector< double > values )
{
dim = shapes.size() ;
nbelements = values.size() ;

Ou, si dim et nbelements sont des membres :

TMatrix::TMatrix( vector< int > shapes , vector< double > values )
: dim( shapes.size() )
, nbelements( values.size() )
{

Tu peux aussi utiliser deux paires d'itérateurs, si tu ne veux pas
te cantoner à un conteneur particulier. Tu pourras alors même
utiliser des tableaux C en arguments. Mais il faut alors ici faire un
constructeur template.

Si le but est de te familiariser avec C++, la solution utilisant
std::vector<> est de loin la plus simple, ÀMHA.



bah, disons me familiariser...mais aussi avoir un code qui marche et qui
soit performant/utilisable par quelqu'un d'autre que moi ;)


Si c'est pas le cas, il vaut mieux utiliser une bib existante, parce que
le probleme a l'air simple a premier abord, mais il se complique tres
rapidement.


erf...justement, y'en a ? ;)


a+, ld.


merci !

Nicolas Bonneel
http://www.byosphere.com


Avatar
Fabien LE LEZ
On Thu, 6 Jan 2005 03:32:25 +0100, "Nicolas Bonneel"
:

Je souhaites realiser la gestion de matrices multidimensionnelles dans mon
programme.


Il y a eu un thread là-dessus ici même, assez complet. Cf
<http://www.google.com/advanced_group_search>.


--
;-)

Avatar
Nicolas Bonneel

On Thu, 6 Jan 2005 03:32:25 +0100, "Nicolas Bonneel"
:


Je souhaites realiser la gestion de matrices multidimensionnelles dans mon
programme.



Il y a eu un thread là-dessus ici même, assez complet. Cf
<http://www.google.com/advanced_group_search>.



un ptit indice siteuplait ?.. Un nom de posteur ? un mot particulier du
topic ? un date approximative....?
Parcequ'une recherche de "tableau" ou "tableau dynamique" ou autre...
sur fr.comp.lang.c++, ca renvoie qd meme qlq milliers de reponses qui
peuvent remonter jusqu'à l'année 1997 alors...

merci ! :)

Nicolas Bonneel
http://www.byosphere.com


Avatar
drkm
writes:

(Il y a une solution encore plus élégant, avec un Shape<N> : la
class Shape<N> contient un Shape<N-1> et un int, et il y a
spécialisation pour Shape<1>. Mais j'avoue que l'écrire dépasse
de loin mes compétences en templates.)


Mais il serait alors forcé de connaître le nombre d'élément d'un
Shape à la compilation. C'est pas un peu contraignant ?

--drkm

Avatar
Loïc Joly
Nicolas Bonneel wrote:



Si c'est pas le cas, il vaut mieux utiliser une bib existante, parce
que le probleme a l'air simple a premier abord, mais il se complique
tres rapidement.



erf...justement, y'en a ? ;)




http://www.oonumerics.org/blitz/

--
Loïc


Avatar
kanze
Nicolas Bonneel wrote:
Desolé pour ma réponse tardive mais mon PC est parti en
reparation, et je viens à peine de rentrer chez mes parents
pour squatter leur pc ;)

Je souhaites realiser la gestion de matrices
multidimensionnelles dans mon programme. J'ai créé un classe
TMatrix, qui comporte le constructeur :

TMatrix::TMatrix(int Shape[], double Valeurs[])


C-à-d :
TMatrix::TMatrix( int* shape, double* valeurs ) ;

{
dim = sizeof(Shape)/sizeof(Shape[0]);


Sur ma machine, en mode 32 bits, ça donnerait toujours 1, et en
mode 64 bits, 2.

nbelements = sizeof(Valeurs)/sizeof(Valeurs[0]);


Sur ma machine, en mode 32 bits, ça donnerait toujours 0, et
en mode 64 bits, 1.


J'avais pourtant vu cette astuce dans l'aide de c++ builder (à
sizeof) pour connaitre le nombre d'elements d'un tableau...


C'est une bonne astuce pour connaître le nombre d'éléments d'un
tableau ; je m'en sers moi-même, quand le code doit être
portable aux compilateurs anciens. (Avec un compilateur qui
comprend bien les templates, il y a de meilleurs solutions, qui
provoque une erreur de compilation dans les cas comme ici, où ça
ne marche pas.) Mais pour connaître le nombrer d'éléments d'un
tableau seulement -- ici, malgré les apparences, tu n'as pas de
tableau, mais un pointeur. Pointeur qui pourrait bien désigner
le premier élément d'un tableau, mais d'un tableau dont il n'y a
aucun moyen de récupérer la taille.

[......]
}


Tu as déjà un problème : si tu te sers des tableaux à la C,
il faut explicitement passer au même temps les longueurs,
parce qu'il n'y a aucun moyen de les récupérer autrement.

Changer la signature du constructeur en :

TMatrix::TMatrix( std::vector< int > const& shape,
std::vector< double > const& valeurs )

résoud cette problème de façon élégante, mais rend l'appel
du constructeur plus pénible (parce que j'imagine qu'assez
souvent, au moins shape est une constante).


ben en faite je comptais pas laisser shape constant... (comme
lorsqu'on utilise la fonction reshape de matlab...) enfin
disons plutot que le parametre shape est constant tout au long
du constructeur, mais le membre _shape ne l'est pas...


Ce que je voulais dire, c'est que tu comptais probablement
appeler le constructeur avec un Shape constante.

La meilleur solution, c'est sans doute de déclarer :

TMatrix::TMatrix( Shape const& shape, Values const& values )

puis de définir Shape et Values avec une variété de
constructeurs pour permettre tous les cas intéressants. Pour
Shape, je ne vois pas trop de problème :

class Shape
{
public:
template< typename FwdIter >
Shape( FwdIter begin, FwdIter end )
: myData( begin, end )
{
}

template< size_t N >
Shape( int const (&init)[ N ] )
: myData( init, init + N )
{
}
// ...

private:
std::vector< int > myData ;
} ;

(Il y a une solution encore plus élégant, avec un Shape<N> :
la class Shape<N> contient un Shape<N-1> et un int, et il y
a spécialisation pour Shape<1>. Mais j'avoue que l'écrire
dépasse de loin mes compétences en templates.)


Cette solution me parait jolie, permettrait de pouvoir faire des
tableaux d'int, de float et de double ...mais le hic c'est que je ne
connais pas du tout le fonctionnement des templates ;)


Faire un tableau templaté sur le type du contenu est en fait
assez simple. Le constructeur templaté que j'ai montré va un peu
plus loin, mais ce n'est pas encore la mer à boire. Ce que je
proposais avec un Shape<N> qui se base sur Shape<N-1>, en
revanche, c'est plutôt pour les experts.

Va faloir que je me documente un peu moi! (enfin dans mon cas,
Shape sera toujours un tableau d'entiers, mais values pourront
etre des reels ou des entiers (ou des complexes... ou peut
être meme d'autres tableaux ?..))


Je comprends. C'est un peu ce auquel je me serais attendu. Mais
si tu régardes ci-dessus, Shape est toujours un std::vector<int>
à la base. Le constructeur templaté, c'est simplement pour
faciliter lui passer un tableau de type C -- grace à la
déduction automatique des paramètres des fonctions templatées,
on n'a pas besoin de spécifier la taille du tableau de type C
explicitement ; le compilateur la calcule pour nous. C-à-d :

static int const dim1[] = { 3, 3, 5 } ;
static int const dim2[] = { 2, 2, 2, 2 } ;
Shape shape1( dim1 ) ;
Shape shape2( dim2 ) ;

Bref, tout ca pour dire que j'ai un peu de mal à voir ce que
le code ci-dessus realise exactement ;)


J'ai du mal à juger ton niveau en C++ en général. Si tu te sens
à l'aise avec du code non-templaté, je te conseille très fort le
Josuttis et Vandevoorte pour les templates. L'astuce ici
consiste à se servir de la déduction automatique des paramètres
d'une fonction templatée, avec le fait que la conversion
implicite tableau en pointeur n'a pas lieu quand on lie le
tableau à une référence. Dans le declaration du constructeur :

template< size_t N >
Shape::Shape( int const (&init)[ N ] )

le compilateur va donc essayer de trouver une instantiation qui
va. Dans la définition de shape1, ci-dessus, la seule
declaration qui va serait avec N == 3. Je ne peux initialiser
« init » avec dim1 que si le type d'init est int const (&)[3].
Le compilateur instantie donc le constructeur avec N = 3.

L'intérêt, évidemment, c'est que dans le constructeur, je peux
me servir de N comme une constante. Ici, on ne se servira
probablement pas du fait que c'est une constante, puisque
l'implémentation serait simplement :

template< size_t N >
Shape::Shape( int const (&init)[ N ] )
: myData( init, init + N )
{
}

En principe, on pouvait se servir de la même technique pour
Values. Seulement, dans le cas de Shape, j'imagine que c'est
plutôt rare que le tableau dépasse une dizaine d'éléments ;
en faire une copie n'est pas la fin du monde. Pour Values,
en revanche, je n'ai pas de mal à imaginer qu'on pourrait
avoir des millions de valeurs, sinon plus. Et même s'il ne
faut pas se précipiter pour s'occuper des problèmes
d'optimisation, des temporaires de cette taille ne sont
jamais une bonne chose. Du coup, il faudrait probablement
s'arranger que Values ne copie pas, mais sait lire les
différentes sources, ce qui est plus difficile. En fin de
compte, beaucoup dépend de ce que tu veux faire avec les
valeurs. S'il suffit de pouvoir les copier dans une
collection d'un type connu d'avance, on doit pouvoir se
servir de l'idiome lettre/envélope, avec une fonction
virtuelle copyInto( DestinationType& dest ).


C'est vrai que values pourra contenir autant d'elements qu'on
souhaite (milliers, millions et plus..?). C'est pour faire une
classe matrix complete où je pourrais deriver une classe
vecteur et une classe matrix2D (pour y faire des
factorisations matricielles, des resolutions etc...)


Je doute que la dérivation soit indiquée. La composition,
plutôt. (Probablement, en tout cas.)

Quand on veut supporter toutes les opérations classiques sur des
matrice, et que les matrices peuvent avoir une certaine taille,
on risque de se heurter à des problèmes de mémoire si on traite
les temporaires de façon naïve. Le problème, c'est que les
alternatifs pour éviter les temporaires intermédiaire sont loin
d'être triviaux.

Alors se posent plusieurs questions :
1- Je ne peux pas créer de matrices en faisant directement :


TMatrix* mat =

new TMatrix({2,2},{1.1 0.5 0.7 0.6});
, je suis obligé de créer des variables int[] et double[] et


les passer

en parametre avec ces valeurs.... Comment puis-je y remedier ?


Pratiquement : tu ne peux pas. Il n'y a simplement pas de
syntax dans le langage pour spécifier une liste de valeurs
d'une longueur arbitraire dans une expression.


ben pourtant on peu declarer : int tableau[] = {1,2,3,4}; non ??


Oui. Mais cette syntaxe n'est permise (actuellement, en tout
cas) que dans l'initialisation dans une définition, et alors
seulement si le type est un « agloméré » (en gros, une struct à
la C ou un tableau à la C, mais jamais une classe avec un
constructeur). Cette syntaxe n'est PAS permise dans une
expression. Et ce que tu passes comme paramètres à un
constructeur sont des expressions.

C99 a quelque chose qui s'appelle compound literals, qui permet
quelque chose du genre -- avec des compound literals et mon
constructeur templaté, on pourrait êcrire quelque chose du
genre :

TMatrix m( Shape( (int[]){ 3, 3, 5 } ), ... ) ;

Mais je me poserais la question de combien de dimensions risque
d'avoir un Shape. S'il n'est que deux ou trois, le cas se règles
facilement au coup de constructeur spécialisé :

Shape::Shape( int ) ;
Shape::Shape( int, int ) ;
Shape::Shape( int, int, int ) ;

Mais à un moment donné, ça cesse d'être jouable. Pour supporter
jusqu'à 100 dimensions, il faudrait écrire 100 constructeurs. En
supposant que le compilateur ne se plaint pas d'une function
avec 100 paramètres.

Pour Shape, je penserais à fournir des constructeurs
spéciaux pour les cas fréquents : Shape( int ), Shape( int,
int ), etc.


Erf, je vois ce que tu veux dire... mais en fait je comptais
laisser shape sous forme de tableau 1D puisque le tableau que
shape décrit peut contenir autant de dimensions qu'on le
souhaite : autant une matrice 4x5 (declarée par un shape(4,5))
qu'une hypermatrice 4x3x2x8x6x5.. declarée shape(4,3,2,8,6,5).


Tout à fait. Interne, dans Shape, tu gardes le std::vector. Les
constructeurs permettent simplement de créer des Shape à une
dimension, à deux dimension, etc., d'une façon plus facile.

C'est vrai que d'un maniere générale shape ne contiendra pas
beaucoup plus de 5-6 elements au grand maximum, mais on sait
jamais ;)


Si on estîme que la vaste majorité des Shape auront 6 éléments
ou moins, rien n'empèche de fournir des constructeurs
spécialisés pour les cas de 1 à 6 éléments, EN PLUS des
constructeurs généralisés avec FwdIter ou un tableau à la C,
pour les rares cas où 6 éléments ne suffira pas, ou les cas
peut-être pas si rare où on ne connaît pas le nombre d'éléments
lor de la compilation.

Jusqu'où tu veux voir dépend de toi, mais j'imagine avec un
et deux int serait un minimum. Et je serais le premier à
avouer que ce n'est pas très élégant de permettre :

TMatrix m( Shape( 5, 10, 3 ), ... )
mais non
TMatrix m( Shape( 5, 10, 3, 2 ), ... )


voilà.. ;)

Une possibilité aussi serait de fornir un constructeur :

Shape::Shape( int first, ... )
: myData( 1, first )
{
va_list a ;
va_start( a, first ) ;
for ( int i = va_arg( a, int ) ;
i > 0 ;
i = va_arg( a, int ) ) {
myData.push_back( i ) ;
}
}

C'est la plus facile à utliser, mais aussi la plus facile
pour faire des connéries. Il exige que la liste des
dimensions soit terminée par une valeur <= 0 ; que
l'utilisateur l'oublie, et tu pars très vite dans les choux.


Là se pose un autre problème : les paramètres proviennent d'un
script interprété. Donc si je connais N paramètres lus du
script, en voulant utiliser le constructeur, je ne saurais pas
trop comment faire ! genre un Shape(param[1],
param[2],...,param[N]) avec N variable...


Si tu ne connais pas Shape lors de la compilation, c'est clair
que le constructeur :

template< typename FwdIter >
Shape::Shape< FwdIter >( FwdIter begin, FwdIter end )
: myData( begin, end )
{
}

est celui qui va servir. À moins que tu fournisse une fonction
push_back à Shape, comme celle de std::vector.

Et évidemment, tout ça ne vaut que pour Shape. Pour Values,
je ne vois pas où s'arrêter avec les constructeurs
surchargés pour un, deux ... etc. paramètres, et évidemment,
il n'y a pas de valeur sentinelle disponible pour la
solution va_args.

2- En passant un Shape de : int[] Shape = {2,2}; lorsque
j'execute le programme, il me met dans dim la valeur 1 (et
quel que soit le vecteur Shape d'ailleurs...). Comment cela se
fait-il ? Comment y remedier ?!


Voir ci-dessus. C'est un problème classique des tableaux de
type C. On ne passe pas des tableaux de type C, on en passe
l'adresse du premier élément. Et même quand on écrit que le
programme prend un tableau comme paramètre, le compilateur
le convertit en pointeur au premier élément. Ce qui veut
dire que pour shape, ton expression vaut « sizeof( int* ) /
sizeof( int ) ».

C'est une des raisons (peut-être la raison la plus
importante) pourquoi on dit d'éviter les tableaux de type C,
et d'utiliser vector à sa place. Seulement, passer des
valeurs qui sont pour toi des constantes, comme tu sembles
vouloir faire, c'est encore plus pénible.


Ben je crois que je vais utiliser le vector moi finallement...


Il vaut mieux. Personnellement, je n'utilise les tableaux à la C
que dans un seul cas : quand il s'agit d'un tableau const à
durée de vie statique, dont les éléments sont des PODs, et les
valeurs d'initialisation des expressions constantes. C-à-d quand
il me garantit l'initialisation statique.

Le fait de passer des vector en parametre du constructeur, ca
les passe par copie ou par reference (comme pour un tableau
C) ?


Comme pour tout type sauf un tableau à la C : par valeur quand
tu démandes par valeur, et par référence quand tu démandes par
référence. En général, il est préférable de passer des
paramètres de type std::vector par référence.

Recopier un vecteur d'1 million d'elements sur un autre,
est-ce une opération longue, ou suffisament bien gérée pour
qu'elle soit rapide ?


Pour les types de base, elle peut être rélativement rapide.
Rélativement -- ce n'est pas gratuit non plus. Mais si tu veux
traiter des très grandes matrices, le problème de la taille
mémoire pourrait se poser indépendamment de la vitesse : l'objet
plus une copie de l'objet a besoin de deux fois plus de mémoire
que l'objet tout seul. Multiplier les copies à outrance peut
finir par épuiser la mémoire.

(disons que l'interet des grosses matrices, c'est de faire des
gros calculs derriere, donc je serai pas à une copie près en
terme de cpu...)


Non, mais en termes d'occupation mémoire, peut-être. Et même --
l'utilisation de multiples objets quand ce n'est pas nécessaire
peut avoir une influence très négative sur la localité, ce qui
influe très fort sur les performances réeles. Je crois que
prèsque toutes les bibliothèques réelement utiliser pour ce
genre de chose utilise pas mal d'astuces pour éviter des
temporaires de taille importante. (Je crois que la référence est
Blitz++ (http://www.oonumerics.org/blitz/), mais ce n'est pas un
domaine d'application que je suis de près.)

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



1 2