OVH Cloud OVH Cloud

array bound forbidden after parenthesized type-id

57 réponses
Avatar
Lucas Levrel
Bonjour,

J'utilise des objets alloués dynamiquement comme suit :
double (*data)[3];
data=new double[t][3];

Maintenant, je voudrais en faire un tableau. J'essaye donc :

double (**tab)[3];
tab=new (double(*)[3])[n];
for(int i=0;i<n;i++) tab[i]=new double[t][3];

et le compilateur (g++) me donne l'erreur que j'ai mise en sujet.
Est-ce que le code suivant, qui compile sans erreur, est équivalent à ce
que je veux faire ?

typedef double(*data_type)[3];
data_type *tab;
tab=new data_type[n];
for...

Si oui, pourquoi le compilateur refuse-t-il le premier code et pas le
second ?

Merci d'avance.
--
LL

10 réponses

1 2 3 4 5
Avatar
Fabien LE LEZ
On Wed, 22 Jul 2009 17:19:50 +0200, (Pascal J.
Bourguignon):

En fait, rien n'empêche de définir une classe (template) specifique
pour des tableaux multidimentionnel.



Bien sûr. Je ne compte plus le nombre de fois où j'ai écrit quelque
chose comme :

class MonTableau2D
{
public:
(plein de trucs)
private:
std::vector < std::vector < machin > > data;
};

La classe en question fournit l'interface ; std::vector<> s'occupe de
gérer la mémoire et l'indexation.

qu'ils ont appris à faire en C...



Je ne crois pas avoir déjà utilisé des tableaux multidimensionnels en
C (du moins, pas alloués dynamiquement). Mais bon, mon expérience de C
est très très limitée.
Avatar
Fabien LE LEZ
On Wed, 22 Jul 2009 18:10:39 +0200, Lucas Levrel
:

- Qu'est-ce que reinterpret_cast<> ? (Quelle différence par rapport au
cast « bête » ?)



Tu poses la question à l'envers.

static_cast<> est un cast "naturel", i.e. prévu par le type. Par
exemple, expliciter le cast d'un int vers un double.

dynamic_cast<> essaie de transformer un pointeur (ou une référence)
sur une classe de base, vers un pointeur (ou une référence) sur une
classe dérivée.
Par exemple :

class Base ...
class Derivee: public Base ...

Derivee d;

Base* p_b= d; // Le passage de "Derivee" à "Base" est implicite. En
fait, c'est l'équivalent d'un static_cast<Base*>(d). Note que p_b
pointe en fait vers un objet créé comme "Derivee".

Derivee* p_d= dynamic_cast<Derivee*>(p_b); // Normalement, on ne peut
pas caster un pointeur sur Base, vers un pointeur sur Derivee. Mais
comme l'objet est un "Derivee" à l'origine, dynamic_cast<> sait le
faire.


reinterpret_cast<> est proche de ce qui se fait en assembleur : il
s'agit de dire au compilateur "Tu crois que le contenu de cette
variable est de type X, mais c'est en fait un objet de type Y".

Caster un double* vers un char* n'a normalement pas de sens, mais
comme on s'intéresse à l'adresse mémoire uniquement, on demande au
compilateur d'"oublier" le type.


Enfin, const_cast<> permet de transformer un pointeur const en un
pointeur non-const. Il y a relativement peu de cas où ça mène à
quelque chose de bon.



Quand tu écris un cast "à la C", qu'est-ce que ce cast fait
exactement ? Peut-être un static_cast<>, peut-être un
reinterpret_cast<>, peut-être un const_cast<>. On ne peut pas le
savoir en lisant le code. Parfois même, celui qui écrit le code ne se
rend pas bien compte de ce qu'il fait.
D'où mon utilisation de reinterpret_cast<>, qui dit explicitement :
"Il faut oublier que le pointeur est un double*, et juste le
considérer comme un emplacement en mémoire."
Avatar
Fabien LE LEZ
On Wed, 22 Jul 2009 18:10:39 +0200, Lucas Levrel
:

- Au fait, dans cette classe que tu proposes, quel est l'avantage
d'utiliser un vector plutôt que T *data et
data=new T[taille1_*taille2_*taille3_] ?



Avec new, il faut rajouter delete au bon endroit. Ou plutôt, aux bons
endroits. C'est se compliquer la vie pour rien, vu que vector<> fait
tout tout seul comme un grand, avec les garanties que tu veux.



Prends le code suivant :

class Broken
{
public:
Broken (size_t taille)
{
data= new int [taille];
}

~Broken()
{
delete[] data;
}

private:
int* data;
};

Non seulement il est plus compliqué (un destructeur en plus), mais en
plus, le constructeur de copie et l'opérateur de copie, générés
automatiquement, sont faux.

int main()
{
Broken b1 (42);
Broken b2= b1; // Simple copie du pointeur "data"
}// Ici, "delete[] b1.data" est appelé, puis "delete[] b2.data". Sauf
que les deux pointeurs pointent vers la même adresse !

Pour contourner le problème, il faut définir soi-même le constructeur
de copie, et l'opérateur de copie. C'est faisable, mais pas trivial.
Ou bien, les déclarer privés.

Ça fait beaucoup de boulot. C'est bien plus simple et efficace de
laisser vector<> s'occuper de tout ça.


Je ne crois pas avoir rencontré de cas où new[] a une utilité. (Ce qui
ne veut pas dire que ça n'existe pas.)
Avatar
James Kanze
On Jul 22, 11:56 am, Fabien LE LEZ wrote:
On Wed, 22 Jul 2009 10:47:13 +0200,
(Pascal J. Bourguignon):



>En fait, ici j'ai l'impression que Lucas veut non pas des vecteurs,



Possible. Je n'ai jamais parlé de vecteurs.



>mais des tableaux multidimentionnel, ou peut être seulement des
>matrices.



Un tableau de T à une dimension s'écrit vector<T>.
Un tableau de T à deux dimensions s'écrit vector< vector<T> >.
Un tableau de T à trois dimensions s'écrit
vector< vector< vector<T> > >.
etc.



Une matrice au sens mathématique est généralement une
encapsulation d'un vector< vector<T> >.



Ou simplement un vector< T >, avec calcule de l'indice. Sans
parler de la posibilité d'utilise valarray à la place de vector.

--
James Kanze (GABI Software) email:
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
James Kanze
On Jul 22, 1:17 pm, Lucas Levrel wrote:
Merci pour votre aide !



En fait je veux un tableau à 3 dimensions n*t*3, et comme n et
t sont très grands, je ne veux pas qu'il occupe beaucoup plus
que n*t*3*64 bits. Sinon j'aurais fait :
double ***tab;
tab=new (double **)[n];
for(int i=0;i<n;i++){
tab[i]=new (double *)[t];
for(int j=0;j<t;j++) tab[i][j]=new double[3];}



mais (sauf erreur dans mes tests) new double[3] consomme 4*64 bits.



Ça dépend beaucoup de l'implémentation. On ne peut rien dire
d'office, sauf que le "double[3]" occupe bien 3*sizeof(double).

De plus, ça m'arrange que les doubles soient contigus par paquets de t* 3,
pour faire une écriture non formatée comme :
file_out.write((char*)tab[i],t*3*sizeof(double));



Ce qui ne marche en aucun cas. (Enfin, ça marche dans le sens
qu'il n'y a pas d'échec à l'écriture, et que tu peux relire les
données à partir du même programme qui les a écrit. Mais il
suffit de récompiler le programme, et il n'y a plus de
garantie.)

Et aussi, std::vector a une garantie de contiguité auss.

Tout ça m'a conduit à l'idée de faire des new double[t][3] et
un tableau de n pointeurs vers ceux-ci.



Pourquoi le tableau de pointeurs ?

Mais, si ça existe, je suis preneur d'une façon de faire plus
lisible et pas significativement plus gourmande en mémoire.
Comme l'a deviné Pascal, les dimensions n et t sont
constantes.



Je ne sais pas encore exactement quelle structure de données tu
veux. double[n][t][3], avec la garantie que double[3][t] soit
contigu ? Dans ce cas-là :

template< typename T >
class Matrix3
{
public:
explicit Matrix3(
int t,
T const& initialValue = T() )
: myData( 3 * t, initialValue )
{
}
Matrix3Proxy operator[]( int i )
{
return Matrix3Proxy( myData, i ) ;
}
Matrix3ConstProxy operator[]( int i ) const
{
return Matrix3ConstProxy( myData, i ) ;
}

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

Dans le cas le plus simple, les Proxy pourrait n'être que des
pointeur. (Dans de tels cas, évidemment, les « constructeurs »
serait plutôt « &myData[ 3*i ] ».) Mais je ferais plus propre,
avec des vraies classes, au moins dans un premier temps.

Ensuite, évidemment, tu auras des vector< Matrix3 >.

--
James Kanze (GABI Software) email:
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
James Kanze
On Jul 22, 4:28 pm, (Pascal J. Bourguignon)
wrote:
Lucas Levrel writes:
> 1)
> double **tab;
> tab=new (double *)[m];
> for(row=0;row<m;row++) tab[row]=new double[n];
> Puis accès à l'élément (i,j) par tab[i][j]



> 2)
> double *tab;
> tab=new double[m*n];
> int index(int row,int col){return row*n+col;};
> Puis accès à l'élément (i,j) par tab[index(i,j)]



> Par la méthode 1, l'accès se fait par deux déréférencements
> successifs. Par la méthode 2, il faut une multiplication,
> une addition, et un déréférencement. Si l'on étend ces
> méthodes au cas à D dimensions, la méthode 1 requiert D
> déréférencements, la méthode 2 (D-1) multiplications et
> additions, et un déréférencement. Qu'est-ce qui est le plus
> rapide ?



La seconde option est et sera de plus en plus la plus
rapide.http://www.cs.virginia.edu/papers/Hitting_Memory_Wall-wulf94.pdf



La différence dépend beaucoup du processeur. Et je ne vois pas
de rapport avec le papier que tu cites. (En fait, je ne vois pas
un rapport avec ce papier et quoique ce soit, étant donné qu'il
ne contient pas d'argument réel.)

--
James Kanze (GABI Software) email:
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
James Kanze
On Jul 22, 5:09 pm, Fabien LE LEZ wrote:
On Wed, 22 Jul 2009 15:42:12 +0200, Lucas Levrel
:



>Merci ! Et pour mon instruction d'écriture, je fais simplement
>fileout.write((char*) &tab(i,0,0), 3*t*sizeof(double));



La fonction write() ne doit pas modifier le tableau, donc le type est
"char const*", pas "char*".



Note par ailleurs que toutes les données sont contiguës en mémoire. Tu
peux donc écrire :



fileout.write (reinterpret_cast<char const*>(&tab(0,0,0)),
n*3*t*sizeof(double));



À condition de ne pas vouloir rélire les données plus tard, avec
un autre programme, ou sur une autre machine.

>Qu'est-ce qui est le plus rapide ?



Faut mesurer. C'est la seule façon d'être sûr.



Et cette mesure ne vaudrait que sur cette machine, avec ce
compilateur, avec les options choisies pour cette compilation.

--
James Kanze (GABI Software) email:
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
James Kanze
On Jul 22, 5:19 pm, (Pascal J. Bourguignon)
wrote:
Fabien LE LEZ writes:



En fait, rien n'empêche de définir une classe (template)
specifique pour des tableaux multidimentionnel. C'est juste
une question d'imagination, mais il semble bien que les
programmeurs C++ n'en soit pas capables, et se ramênent
toujours à des vecteurs de vecteurs qu'ils ont appris à faire
en C...



C'est plutôt une question de ne pas réinventer la roue. C'est un
peu stupide de réécrire un tas de code, quand j'ai une
implémentation tout faite qui marche.

--
James Kanze (GABI Software) email:
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
James Kanze
On Jul 22, 12:59 pm, Lucas Levrel
wrote:
Le 21 juillet 2009, James Kanze a écrit :



> Quant à l'erreur, il aurait été plus simple si tu avais
> indiqué quelle ligne provoquait l'erreur,



Ah oui, désolé ! C'est cette ligne qui coince :
tab=new (double(*)[3])[n];



> C-à-d que ta ligne se parse :



> tab = (new (double (*)[3])) [n] ;



> Le [n] n'apppartient pas à l'expression new.



OK. Pour me fixer les idées, quand on fait :
toto = new double[n];
c'est bien parsé comme new (double[n]) et non pas (new
double)[n] ?



Oui et non. Si je régarde la grammaire, « new double[n] » se
reduit à « new new-type-id », « new (double[n]) » à «  new
( type-id ) ». Mais le [n] fait bien partie de l'expression new
dans les deux cas.

Le problème dans ton expression de new, c'est que le new-type-id
est très limité. Il ne peut comporter qu'une sequence d'un ou
plus type-specifier (des choses comme int, const ou MyClass),
suivi de zéro ou plus *, suivi de zéro ou plus [n] (dont tous
sauf le dernier doivent être constante) ; le but, c'est de le
rendre possible que le compilateur trouve une fin de
l'expression bien définie. (Si le langage permettais des
parenthèses, par exemple, la distinction entre « new
double(*p) » et « new double(*) » serait un peu délicate.)

> Si tu y tiens,
> tab=new (double((*[n])[3])) ;
> doit faire l'affaire. Mais je n'aimerais pas !a avoir à
> maintenir de tel code.



Effectivement. Ça crée bien un tableau de n objets de type
double(*)[3] ?



Oui. On commence par le français : un tableau[n] de pointeurs à
un tableau[3] de double. Qu'on converte en C++ de gauche à
droite, en insérant les parenthèses quand il le faut :

un tableau[n] de [n]
pointeurs à *[n]
un tableau[3] de (*[n])[3]
(ici, il faut les parenthèse, parce que sinon,
« tableau » s'évalue avant « pointeur »)
double double (*[n])[3]

(En fait, j'avais une paire de parenthèse supplémentaire.)
Ensuite, si c'est pour un new, et que l'expression ne correspond
pas un un new-type-id (nom(s) de type, suivi des *, suivi des
[], mais jamais de ()), il faut mettre l'ensemble dans des
parenthèses, pour qu'il soit interprêté comme un type-id, et non
un new-type-id.

Mais comme a dit Fabien, il vaut mieux utiliser une
std::vector<double>, dans une classe qui l'emballe pour faire ce
que tu veux. C'est plus facile à écrire, plus facile à lire, et
plus robuste.

--
James Kanze (GABI Software) email:
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
Fabien LE LEZ
On Thu, 23 Jul 2009 00:34:46 -0700 (PDT), James Kanze
:

À condition de ne pas vouloir rélire les données plus tard, avec
un autre programme, ou sur une autre machine.



On n'a pas beaucoup de garanties, effectivement.
Néanmoins, il s'agit d'enregistrer des "double", contigus. Il y a de
bonnes chances pour que ça fonctionne bien tant qu'on reste sur des
architectures i386 ou AMD64. Et dans pas mal de cas, ça suffit
largement.

Et cette mesure ne vaudrait que sur cette machine, avec ce
compilateur, avec les options choisies pour cette compilation.



C'est déjà ça.
1 2 3 4 5