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.)
Premièrement, une petite classe de test. Mon tableau va être 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 = 0, string st = "vide");
~TestClass(void); TestClass(const TestClass &source);
void operator = (const TestClass &source);
friend ostream& operator << (ostream& os, TestClass &source); //j'ai
surchargé << mais c'est juste pour tester
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;
}
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>
#include <vector>
using namespace std;
template <typename T> class Dyn2Dvector
{
public:
Dyn2Dvector(T* neutralElement = NULL)
{
m_pNeutralElement = ( neutralElement == NULL ) ? new T() :
neutralElement;
}
virtual ~Dyn2Dvector(){}
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) )
{ //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
}
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;
}
}
private:
vector<vector<T>> m_array;
T* m_pNeutralElement;
};
#endif
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.)
Premièrement, une petite classe de test. Mon tableau va être 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 = 0, string st = "vide");
~TestClass(void); TestClass(const TestClass &source);
void operator = (const TestClass &source);
friend ostream& operator << (ostream& os, TestClass &source); //j'ai
surchargé << mais c'est juste pour tester
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;
}
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>
#include <vector>
using namespace std;
template <typename T> class Dyn2Dvector
{
public:
Dyn2Dvector(T* neutralElement = NULL)
{
m_pNeutralElement = ( neutralElement == NULL ) ? new T() :
neutralElement;
}
virtual ~Dyn2Dvector(){}
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) )
{ //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
}
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;
}
}
private:
vector<vector<T>> m_array;
T* m_pNeutralElement;
};
#endif
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.)
Premièrement, une petite classe de test. Mon tableau va être 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 = 0, string st = "vide");
~TestClass(void); TestClass(const TestClass &source);
void operator = (const TestClass &source);
friend ostream& operator << (ostream& os, TestClass &source); //j'ai
surchargé << mais c'est juste pour tester
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;
}
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>
#include <vector>
using namespace std;
template <typename T> class Dyn2Dvector
{
public:
Dyn2Dvector(T* neutralElement = NULL)
{
m_pNeutralElement = ( neutralElement == NULL ) ? new T() :
neutralElement;
}
virtual ~Dyn2Dvector(){}
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) )
{ //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
}
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;
}
}
private:
vector<vector<T>> m_array;
T* m_pNeutralElement;
};
#endif
Vu que c'est pour un exemple didactique, je vais faire beaucoup de
commentaires.
~TestClass(void); TestClass(const TestClass &source);
Le formatage est étrange: j'aurais fait deux lignes de la précédent e.
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.
Dyn2Dvector(T* neutralElement = NULL)
{
m_pNeutralElement = ( neutralElement == NULL ) ? new T() :
neutralElement;
}
Pourquoi pas une liste d'initialisation?
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.
Pourquoi agrandir des lignes qui n'en n'ont pas besoin?
void display() //juste pour les tests
Est-ce qu'une telle fonction membre a réellement un intérêt d'êtr e membre?
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) 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=.
Vu que c'est pour un exemple didactique, je vais faire beaucoup de
commentaires.
~TestClass(void); TestClass(const TestClass &source);
Le formatage est étrange: j'aurais fait deux lignes de la précédent e.
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.
Dyn2Dvector(T* neutralElement = NULL)
{
m_pNeutralElement = ( neutralElement == NULL ) ? new T() :
neutralElement;
}
Pourquoi pas une liste d'initialisation?
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.
Pourquoi agrandir des lignes qui n'en n'ont pas besoin?
void display() //juste pour les tests
Est-ce qu'une telle fonction membre a réellement un intérêt d'êtr e membre?
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) 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=.
Vu que c'est pour un exemple didactique, je vais faire beaucoup de
commentaires.
~TestClass(void); TestClass(const TestClass &source);
Le formatage est étrange: j'aurais fait deux lignes de la précédent e.
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.
Dyn2Dvector(T* neutralElement = NULL)
{
m_pNeutralElement = ( neutralElement == NULL ) ? new T() :
neutralElement;
}
Pourquoi pas une liste d'initialisation?
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.
Pourquoi agrandir des lignes qui n'en n'ont pas besoin?
void display() //juste pour les tests
Est-ce qu'une telle fonction membre a réellement un intérêt d'êtr e membre?
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) 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.
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.
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.
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"?
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.
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'ê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.
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 ().
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
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"?
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.
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'ê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.
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 ().
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
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"?
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.
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'ê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.
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 ().
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 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.
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.
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.
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'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'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.