OVH Cloud OVH Cloud

tableau à 2 dimensions qui utilise la STL

7 réponses
Avatar
r0d
Bonjour =E0 tous,

je viens d'impl=E9menter une petite classe template qui n'est rien de
plus qu'un tableau =E0 2 dimensions, et qui utilise les std::vector, et
j'aurais aim=E9 avoir vos critiques et propositions pour l'am=E9liorer.

Je sais qu'il existe un template qui fait =E0 peu pr=E9s la m=EAme chose
dans boost, mais il n'est pas toujours =E9vident d'installer boost, et
la classe que je propose ici est =E0 but didactique (utilisation de
vector, templates, etc.)

Premi=E8rement, une petite classe de test. Mon tableau va =EAtre rempli
par des instances de cette classe:

TestClass.h
------------------
#ifndef __TEST_CLASS__
#define __TEST_CLASS__

#include <string>
using namespace std;

class TestClass
{
public:
TestClass(int val =3D 0, string st =3D "vide");
~TestClass(void); TestClass(const TestClass &source);

void operator =3D (const TestClass &source);
friend ostream& operator << (ostream& os, TestClass &source); //j'ai
surcharg=E9 << mais c'est juste pour tester

int value;
string str;
};

#endif
---------------------

TestClass.cpp
---------------------
#include "TestClass.h"

TestClass::TestClass(int val/* =3D 0*/, string st/* =3D "vide"*/)
: value(val), str(st) { }

TestClass::~TestClass() { }

TestClass::TestClass(const TestClass &source)
{
this->value =3D source.value;
this->str =3D source.str;
}

void TestClass::operator =3D(const TestClass &source)
{
this->value =3D source.value;
this->str =3D source.str;
}

ostream& operator << (ostream& os, TestClass &source)
{
os << source.str;
return os;
}
---------------------------------------------------------------------------=
--

Ensuite, le tableau en lui-m=EAme:
DynADvector.h
---------------------
// Template class Dyn2Dvector //
// * la construction de ce tableau se fait de mani=E8re =E0 ce que chaque
ligne (row) comporte
// le m=EAme nombre de colonnes (col).
// * le type contenu dans le tableau doit avoir:
// -> un constructeur par d=E9faut,
// -> un constructeur de recopie,
// -> un op=E9rateur d'affectation.
// * il est possible de d=E9finir un =E9l=E9ment neutre (neutralElement)
dans le constructeur, cet
// =E9l=E9ment sert =E0 remplir les trous du tableau. S'il n'est pas
d=E9fini, l'=E9l=E9ment neutre est
// construit =E0 partir du constructeur par d=E9faut du type contenu dans
le tableau.

#ifndef __DYN2DVECTOR_H__
#define __DYN2DVECTOR_H__

#include <iostream>
#include <vector>

using namespace std;

template <typename T> class Dyn2Dvector
{
public:
Dyn2Dvector(T* neutralElement =3D NULL)
{
m_pNeutralElement =3D ( neutralElement =3D=3D NULL ) ? new T() :
neutralElement;
}

virtual ~Dyn2Dvector(){}

T getAt(int row, int col)
{
if (m_array.size()<row)
return *m_pNeutralElement;

if (m_array[row].size()<col)
return *m_pNeutralElement;

return m_array[row][col];
}

void setAt(int row, int col, T item)
{
if ( ( (int) m_array.size()<=3Drow) || ( (int)
m_array[row].size()<=3Dcol) )
{ //si le tableau n'est pas assez grand, je le 'resize'
for (int curRow=3D0; curRow<=3Drow; curRow++)
{
if ((int) m_array.size()<=3DcurRow)
{// s'il n'y a pas assez de lignes, je rajoute une ligne
vector<T> newRow;
for (int i=3D0; i<col; i++)
newRow.push_back(*m_pNeutralElement);

if (curRow=3D=3Drow)
newRow.push_back(item);
else
newRow.push_back(*m_pNeutralElement);

m_array.push_back(newRow);
}
else if ((int) m_array[curRow].size()<=3Dcol)
{//sinon, j'aggrandis la ligne
int curRowSize =3D (int) m_array[curRow].size();

for (int k=3DcurRowSize; k<col; k++)
m_array[curRow].push_back(*m_pNeutralElement);

if (curRow=3D=3Drow)
m_array[curRow].push_back(item);
else
m_array[curRow].push_back(*m_pNeutralElement);
}
}
}
else
{ // sinon j'affecte simplement l'=E9l=E9ment
m_array[row][col] =3D item;
}
}

void display() //juste pour les tests
{
for (int i=3D0; i<(int) m_array.size(); i++)
{
for (int j=3D0; j<(int) m_array[i].size(); j++)
{
cout << getAt(i, j) << " ";
}
cout << endl;
}
}

private:
vector<vector<T>> m_array;
T* m_pNeutralElement;
};

#endif

---------------------------------------------------------------------------=
--------------

Et finalement, quelque ligne pour tester tout =E7a:
----------------------------
TestClass* pNe =3D new TestClass();
Dyn2Dvector<TestClass> myVector(pNe);
TestClass n1;

n1.str =3D "prim";
n1.value =3D 4;
myVector.setAt(2, 3, n1);

n1.str =3D "deuz";
n1.value =3D 6;
myVector.setAt(3, 5, n1);

n1.str =3D "troi";
n1.value =3D 4;
myVector.setAt(2, 2, n1);

myVector.display();

getchar();
---------------------------------

Voil=E0voil=E0, toute critique est la bienvenue. Merci pour votre
participation :)

7 réponses

Avatar
Jean-Marc Bourguet
"r0d" writes:

Bonjour à tous,

je viens d'implémenter une petite classe template qui n'est rien de
plus qu'un tableau à 2 dimensions, et qui utilise les std::vector, et
j'aurais aimé avoir vos critiques et propositions pour l'améliorer.

Je sais qu'il existe un template qui fait à peu prés la même chose
dans boost, mais il n'est pas toujours évident d'installer boost, et
la classe que je propose ici est à but didactique (utilisation de
vector, templates, etc.)


Vu que c'est pour un exemple didactique, je vais faire beaucoup de
commentaires.

Premièrement, une petite classe de test. Mon tableau va être rempli
par des instances de cette classe:

TestClass.h
------------------
#ifndef __TEST_CLASS__


Ne pas utiliser d'identificateurs contenant un double _ ou commencant par
un _.

La plupart sont réservés pour l'implémentation dans un ou plusieurs
contextes et je ne trouve pas que ça vaille la peine d'apprendre les
détails.

#define __TEST_CLASS__

#include <string>
using namespace std;


Ne jamais mettre une clause using dans un en-tête. C'est l'imposé à tous
ceux qui l'inclueront et elle pourra leur poser des problèmes (*). Je ne
suis d'ailleurs pas sûr qu'une clause using dans un .cpp soit une bonne
idée; mais au moins les problèmes causés par cette clause sont alors
circonscrit à ce .cpp (mais il peuvent apparaître suite à un changement
apparemment mineur dans un en-tête inclu).

class TestClass
{
public:
TestClass(int val = 0, string st = "vide");
~TestClass(void); TestClass(const TestClass &source);


Le formatage est étrange: j'aurais fait deux lignes de la précédente.

void operator = (const TestClass &source);
friend ostream& operator << (ostream& os, TestClass &source); //j'ai
surchargé << mais c'est juste pour tester


Attention à l'homogénéité du formatage.
Si tu utilises ostream, il faut avoir inclus <iosfwd> ou <ostream>.

int value;
string str;
};

#endif
---------------------

TestClass.cpp
---------------------
#include "TestClass.h"

TestClass::TestClass(int val/* = 0*/, string st/* = "vide"*/)
: value(val), str(st) { }

TestClass::~TestClass() { }

TestClass::TestClass(const TestClass &source)
{
this->value = source.value;
this->str = source.str;
}


Pourquoi this->?
Pourquoi ne pas utiliser une liste d'initialisation comme dans le
constructeur précédant.

void TestClass::operator =(const TestClass &source)
{
this->value = source.value;
this->str = source.str;
}

ostream& operator << (ostream& os, TestClass &source)
{
os << source.str;
return os;
}
-----------------------------------------------------------------------------

Ensuite, le tableau en lui-même:
DynADvector.h
---------------------
// Template class Dyn2Dvector //
// * la construction de ce tableau se fait de manière à ce que chaque
ligne (row) comporte
// le même nombre de colonnes (col).
// * le type contenu dans le tableau doit avoir:
// -> un constructeur par défaut,
// -> un constructeur de recopie,
// -> un opérateur d'affectation.
// * il est possible de définir un élément neutre (neutralElement)
dans le constructeur, cet
// élément sert à remplir les trous du tableau. S'il n'est pas
défini, l'élément neutre est
// construit à partir du constructeur par défaut du type contenu dans
le tableau.

#ifndef __DYN2DVECTOR_H__
#define __DYN2DVECTOR_H__

#include <iostream>


Inclure <iostream> dans un en-tête est rarement la bonne chose à faire. En
particulier il peut ne pas définir ostream et istream ce qui est
généralement ce qui était désiré.

<iosfwd> suffit souvent, <istream> et <ostream> dans les autres cas.

#include <vector>

using namespace std;

template <typename T> class Dyn2Dvector
{
public:
Dyn2Dvector(T* neutralElement = NULL)
{
m_pNeutralElement = ( neutralElement == NULL ) ? new T() :
neutralElement;
}


Pourquoi pas une liste d'initialisation?

virtual ~Dyn2Dvector(){}


Qui libère m_pNeutralElement?

Pourquoi virtuel? Ta classe a un opérateur d'assignement et un
constructeur de copie -- d'ailleurs problèmatiques puisque générés par
défaut et qu'il y a un membre pointeur -- ce qui s'accorde généralement mal
avec un comportement polymorphe.

T getAt(int row, int col)
Pourquoi n'est-ce pas un membre const?

{
if (m_array.size()<row)
return *m_pNeutralElement;

if (m_array[row].size()<col)
return *m_pNeutralElement;

return m_array[row][col];
}

void setAt(int row, int col, T item)
{
if ( ( (int) m_array.size()<=row) || ( (int)
m_array[row].size()<=col) )


Pourquoi ces casts vers int?

{ //si le tableau n'est pas assez grand, je le 'resize'
for (int curRow=0; curRow<=row; curRow++)
{
if ((int) m_array.size()<=curRow)
{// s'il n'y a pas assez de lignes, je rajoute une ligne
vector<T> newRow;
for (int i=0; i<col; i++)
newRow.push_back(*m_pNeutralElement);
Regarde le constructeur qui a deux arguments


if (curRow==row)
newRow.push_back(item);
else
newRow.push_back(*m_pNeutralElement);



m_array.push_back(newRow);
}
else if ((int) m_array[curRow].size()<=col)
{//sinon, j'aggrandis la ligne
Pourquoi agrandir des lignes qui n'en n'ont pas besoin?


int curRowSize = (int) m_array[curRow].size();

for (int k=curRowSize; k<col; k++)
m_array[curRow].push_back(*m_pNeutralElement);

if (curRow==row)
m_array[curRow].push_back(item);
else
m_array[curRow].push_back(*m_pNeutralElement);
}
}
}
else
{ // sinon j'affecte simplement l'élément
m_array[row][col] = item;
}
Généralement quand dans un if on a deux branches très inégales, on met la

plus petite en premier.

À vrai dire, plutôt que de compliquer la logique comme tu l'as fait
j'aurais séparé l'agrandissement (qui aurait toujours ajouter l'élément
neutre) et l'assignement qui ne se serait donc pas retrouvé dans une
branche else.

}

void display() //juste pour les tests
{
for (int i=0; i<(int) m_array.size(); i++)
{
for (int j=0; j<(int) m_array[i].size(); j++)
{
cout << getAt(i, j) << " ";
}
cout << endl;
}
}


Est-ce qu'une telle fonction membre a réellement un intérêt d'être membre?
Au strict minimum il faudrait prendre un ostream en paramètre.

L'impossibilité de l'écrire comme fonction non membre est révélateur de
déficiences dans l'interface de la classe.

On se demande pourquoi ce n'est pas un membre const.

Quel est l'intérêt d'utiliser endl dans ce contexte? Si un flush est
nécessaire, le faire après la deuxième boucle.

Pourquoi ces casts!

Note: le fait qu'elle utilise cout rend l'inclusion d'<iostream>
nécessaire.

private:
vector<vector<T>> m_array;


Il manque un espace entre les deux >.

T* m_pNeutralElement;


Pourquoi est-ce un pointeur? Il me semble que ça n'apporte que des
problèmes.

};


J'aurais choisi une interface plus proche de la STL, en fournissant entre
autres des itérateurs.

J'aurais utilisé operator() plutôt que getAt. L'utiliser aussi pour setAt
(en le faisant retourner une référence dans une version non const) est à
envisager. Mais cela pose le problème que ça force l'agrandissement dans
des contextes qui sont de simples lectures. Si on veut pousser l'exercice
un peu plus loin -- on est dans un exercice didactique, non? -- comparer la
solution retournant une référence et celle retournant un proxy qui a un
operateur=.

#endif


(*) Ce n'est pas pour autant une bonne idée d'avoir d'utiliser des
identificateurs de la SL avec un autre sens; mais on a parfois pas le
choix, rares sont les gens connaissant tous les identificateurs de la SL,
et personne ne sait ce qui sera ajouté dans std.

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
r0d
Vu que c'est pour un exemple didactique, je vais faire beaucoup de
commentaires.


Yaisse!! Je vous remercie infiniment de vous être penché sur mon
code, j'avoue que je n'y croyais pas!! :)

~TestClass(void); TestClass(const TestClass &source);


Le formatage est étrange: j'aurais fait deux lignes de la précédent e.


Problème de copier/coller. J'ai trop l'habitude des forums, du coup
j'ai du mal avec les ng. Désolé.

void operator = (const TestClass &source);
friend ostream& operator << (ostream& os, TestClass &source); //j'ai
surchargé << mais c'est juste pour tester


Attention à l'homogénéité du formatage.


Que voulez-vous dire? Qu'entendez-vous par "homogénéité du
formatage"?

Dyn2Dvector(T* neutralElement = NULL)
{
m_pNeutralElement = ( neutralElement == NULL ) ? new T() :
neutralElement;
}


Pourquoi pas une liste d'initialisation?


En vérité, la construction de cette classe me pose problème.
Visiblement, je m'y suis mal pris. La présence de ce pointeur en
témoigne. Je vais revoir ça.

virtual ~Dyn2Dvector(){}


Pourquoi virtuel? Ta classe a un opérateur d'assignement et un
constructeur de copie -- d'ailleurs problèmatiques puisque généré s par
défaut et qu'il y a un membre pointeur -- ce qui s'accorde générale ment mal
avec un comportement polymorphe.


J'aimerais bien que ma classe (contrairement aux conteneurs de la SL)
soit parfaitement dérivable. Mon idée étant que l'utilisateur de
cette classe n'ait pas à modifier le code s'il souhaite ajouter des
fonctionnalités. Je ne sais pas si c'est une bonne façon de voir les
choses.

Pourquoi agrandir des lignes qui n'en n'ont pas besoin?



Alors ça c'est un choix de conception. Peut-être est-il mauvais, je
ne sais pas. L'idée est de faire en sorte que cette classe soit la
plus simple à utiliser. Aggrandir les ligne qui n'en ont pas besoin
permet de conserver une forme "rectangulaire" (je ne sais pas si c'est
le terme exact) à la matrice.

L'avantage à ce que la matrice reste rectangulaire est que ça
facilitera l'implémentation des opérations qui seront effectuées
dessus. C'est la même raison qui m'a fait prendre la décision de
prévoir un élément neutre.

void display() //juste pour les tests
Est-ce qu'une telle fonction membre a réellement un intérêt d'êtr e membre?



Alors là, je n'y comprends plus rien... depuis que je fais de la poo,
on me dit que chaque classe doit gérer elle-même son comportement.
Pour moi, il paraissait évident que le display devait être membre.
Pourquoi ne le serait-elle pas? D'ailleurs, vous n'êtes pas le seul à
mettre en doute ce point, mais je ne comprend toujours pas pourquoi.

J'aurais choisi une interface plus proche de la STL, en fournissant entre
autres des itérateurs.


En effet. Je voulais éviter l'implémentation d'itérateur pour ne pas
compliquer le code. Mais finalement, cela pourrait être une bonne
idée, car ça en simplifierais l'utilisation et ça ferait un exemple
d'implémentation d'itérateur.

J'aurais utilisé operator() plutôt que getAt. L'utiliser aussi pour setAt
(en le faisant retourner une référence dans une version non const) es t à
envisager. Mais cela pose le problème que ça force l'agrandissement dans
des contextes qui sont de simples lectures. Si on veut pousser l'exercice
un peu plus loin -- on est dans un exercice didactique, non? -- comparer la
solution retournant une référence et celle retournant un proxy qui a un
operateur=.


Là vous m'avez perdu... Déjà, j'ai du mal à voir qu'est-ce
l'utilisation d'un proxy apporterait à cette classe.
Ensuite, je ne vois pas comment m'y prendre pour implémenter le get et
le set avec le même opérateur (). A vrai dire, j'ai déjà remplacé
le get par l'opérateur (), mais je ne vois pas comment faire en sorte
d'en faire également un set.

J'ai fait le tour de ce qui me posait problème. Ce que je n'ai pas
repris, c'est ce que j'ai compris et que j'approuve (j'ai déjà
commencé à modifier mon code).

Vraiment, je vous remercie infiniment pour vos remarques. Si cela vous
intéresse, j'ai posé le même code ici:

http://www.developpez.net/forums/showthread.php?t$1441

Encore merci.

r0d.


Avatar
Loïc Joly

Là vous m'avez perdu... Déjà, j'ai du mal à voir qu'est-ce
l'utilisation d'un proxy apporterait à cette classe.
Ensuite, je ne vois pas comment m'y prendre pour implémenter le get et
le set avec le même opérateur (). A vrai dire, j'ai déjà remplacé
le get par l'opérateur (), mais je ne vois pas comment faire en sorte
d'en faire également un set.


A l'aide d'un proxy justement... ;)

Au lieu de retourner un élément de ta matrice, tu retourne une classe où
tu as surchargé l'opérateur= pour que quand il est appelé, il appèle la
bonne fonction de ta matrice.

Les autres proxys évoqués (je sais plus si c'est ici ou sur developpez)
sont des classes qui te permettent d'itérer au choix sur une ligne ou
une colonne, genre pour écrire du code comme :

Matrice m;
// plein d'init
Colonne c = m.colonne(3);
for (Colonne::iterator it = c.begin()
it != c.end();
++it)
{
*it = 2;
}

Là ta classe Colonne est un proxy vers la matrice, ca elle ne contient
pas vraiment les données, mais elle se comporte de telle façon que quand
l'utilisateur modifie la colonne, il modifie en fait la matrice.

--
Loïc

Avatar
Jean-Marc Bourguet
"r0d" writes:

void operator = (const TestClass &source);
friend ostream& operator << (ostream& os, TestClass &source); //j'ai
surchargé << mais c'est juste pour tester


Attention à l'homogénéité du formatage.


Que voulez-vous dire? Qu'entendez-vous par "homogénéité du
formatage"?


Je pensais en particulier a

ostream& os
TestClass &source

Il faut se decider. L'idiomatique en C++ est le premier.

virtual ~Dyn2Dvector(){}


Pourquoi virtuel? Ta classe a un opérateur d'assignement et un
constructeur de copie -- d'ailleurs problèmatiques puisque générés par
défaut et qu'il y a un membre pointeur -- ce qui s'accorde généralement mal
avec un comportement polymorphe.


J'aimerais bien que ma classe (contrairement aux conteneurs de la SL)
soit parfaitement dérivable. Mon idée étant que l'utilisateur de
cette classe n'ait pas à modifier le code s'il souhaite ajouter des
fonctionnalités.


Quelle fonctionalite veux-tu ajouter a ta classe qui necessite un
destructeur virtuel? Pour rappel, tu n'as aucun autre membre virtuel.
Donc aucun comportement polymorphe possible. Pourquoi alors vouloir
detruire par un pointeur sur la classe de base?

Je ne sais pas si c'est une bonne façon de voir les choses.

Pourquoi agrandir des lignes qui n'en n'ont pas besoin?



Alors ça c'est un choix de conception. Peut-être est-il mauvais, je
ne sais pas. L'idée est de faire en sorte que cette classe soit la
plus simple à utiliser. Aggrandir les ligne qui n'en ont pas besoin
permet de conserver une forme "rectangulaire" (je ne sais pas si c'est
le terme exact) à la matrice.


En quoi ca change l'utilisation?

L'avantage à ce que la matrice reste rectangulaire est que ça
facilitera l'implémentation des opérations qui seront effectuées
dessus. C'est la même raison qui m'a fait prendre la décision de
prévoir un élément neutre.


Je ne vois pas ce qui est possible et qui ne le serait pas.

void display() //juste pour les tests
Est-ce qu'une telle fonction membre a réellement un intérêt d'être membre?



Alors là, je n'y comprends plus rien... depuis que je fais de la poo,
on me dit que chaque classe doit gérer elle-même son comportement.
Pour moi, il paraissait évident que le display devait être membre.
Pourquoi ne le serait-elle pas? D'ailleurs, vous n'êtes pas le seul à
mettre en doute ce point, mais je ne comprend toujours pas pourquoi.


C'est Sutter je crois qui faisait remarquer que les fonctions non membres
definie dans le meme namespace que la classe et ayant un parametre de cette
classe faisaient effectivement partie de l'interface de la classe. Cette
partie de l'interface a l'avantage de ne pas etre fermee. Se pose alors la
question du choix pour une fonctionalite donnee d'en faire un membre ou une
fonction libre. Un critere propose etait de fournir comme membres ce qui
etait strictement necessaire et comme fonctions libres ce qui est plus
accessoire mais neanmoins utile. display se trouve dans ce cas. En plus
pour display, si on doit le rendre friend pour fournir la fonctionalite,
c'est un bon indicateur d'un manque dans l'interface publique.

J'aurais choisi une interface plus proche de la STL, en fournissant entre
autres des itérateurs.


En effet. Je voulais éviter l'implémentation d'itérateur pour ne pas
compliquer le code. Mais finalement, cela pourrait être une bonne
idée, car ça en simplifierais l'utilisation et ça ferait un exemple
d'implémentation d'itérateur.

J'aurais utilisé operator() plutôt que getAt. L'utiliser aussi pour setAt
(en le faisant retourner une référence dans une version non const) est à
envisager. Mais cela pose le problème que ça force l'agrandissement dans
des contextes qui sont de simples lectures. Si on veut pousser l'exercice
un peu plus loin -- on est dans un exercice didactique, non? -- comparer la
solution retournant une référence et celle retournant un proxy qui a un
operateur=.


Là vous m'avez perdu... Déjà, j'ai du mal à voir qu'est-ce
l'utilisation d'un proxy apporterait à cette classe.
Ensuite, je ne vois pas comment m'y prendre pour implémenter le get et
le set avec le même opérateur ().


- en faisant retourner une reference a l'operateur ()

- en lui faisant retourner un proxy (une classe qui ici n'aura pas d'autres
objets que de decouvrir assez du contexte pour implementer le
comportement voulu dans le contexte) qui lui va pouvoir etre un peu plus
fin et ne pas construire un element simplement parce qu'on le lit. Le
plus simple est de definir un operateur= sur le proxy et de lui definir
une conversion implicite en T. C'est un bel exercice de voir a quel
point il est possible (ou impossible) de discerner les lvalues (pour
lesquelles le proxy doit se transformer en une reference) des rvalues
(pour lesquelles les proxy peut se transformer en valeur). Exercice qui
sera a refaire en C++ 2009...

Vraiment, je vous remercie infiniment pour vos remarques. Si cela vous
intéresse, j'ai posé le même code ici:

http://www.developpez.net/forums/showthread.php?t$1441


C'est le genre de post sur lequel je ne reponds pas la. L'interface est
vraiment trop penible pour ca a mon gout pour repondre convenablement.

A+

--
Jean-Marc



Avatar
r0d
Ok, je commence à y voir plus clair.
Vraiment merci mille fois pour votre aide à tous.

Sinon, j'ai fait une erreur dans le lien sur developpez.com que j'ai
fourni. Voilà le bon:
http://www.developpez.net/forums/showthread.php?t$1573
Avatar
Loïc Joly

C'est Sutter je crois qui faisait remarquer que les fonctions non membres
definie dans le meme namespace que la classe et ayant un parametre de cette
classe faisaient effectivement partie de l'interface de la classe. Cette
partie de l'interface a l'avantage de ne pas etre fermee. Se pose alors la
question du choix pour une fonctionalite donnee d'en faire un membre ou une
fonction libre. Un critere propose etait de fournir comme membres ce qui
etait strictement necessaire et comme fonctions libres ce qui est plus
accessoire mais neanmoins utile.


En l'occurence, je crois que c'est Scott Meyers qui a écrit l'article
qui a à l'époque causé pas mal de discussions. Je pense que c'est
http://www.ddj.com/dept/cpp/184401197.

--
Loïc

Avatar
Jean-Marc Bourguet
Loïc Joly writes:

C'est Sutter je crois qui faisait remarquer que les fonctions non membres
definie dans le meme namespace que la classe et ayant un parametre de cette
classe faisaient effectivement partie de l'interface de la classe. Cette
partie de l'interface a l'avantage de ne pas etre fermee. Se pose alors la
question du choix pour une fonctionalite donnee d'en faire un membre ou une
fonction libre. Un critere propose etait de fournir comme membres ce qui
etait strictement necessaire et comme fonctions libres ce qui est plus
accessoire mais neanmoins utile.


En l'occurence, je crois que c'est Scott Meyers qui a écrit l'article qui a
à l'époque causé pas mal de discussions. Je pense que c'est
http://www.ddj.com/dept/cpp/184401197.


C'etait mon second choix :-(

A+

--
Jean-Marc