OVH Cloud OVH Cloud

Comment rendre une classe "iterable"?

36 réponses
Avatar
Michael
Bonsoir à tous,

j'ai les deux classes suivantes:

class VideoDevice
{
private:
std::string name;
public:
std::string & GetName() const { return name; }
};

class VideoDeviceEnumerator
{
public:
void FillList(TBox * box);
void getVideoDevice(const std::string & fooName, VideoDevice * vd);
};

Les deux fonctions de VideoDeviceEnumerator énumèrent en interne tous les
périphériques de capture vidéo disponibles sur le système... Et la fonction
FillList est tout ce qu'il y a de restrictif puisqu'elle ne fonctionne
qu'avec les objets de la VCL de Borland.

Je voudrais donc rendre cette classe identique à un itérateur, pour que
l'utilisateur puisse faire comme avec les conteneurs de la STL...

Un truc du genre:

VideoDeviceEnumerator vdEnum;

for (VideoDeviceEnumerator::constIterator ite = vdEnum.begin(); ite !=
vdEnum.end(); ++ite)
box->AddItem(ite->GetName());

Et pour obtenir un VideoDevice:

VideoDeviceEnumerator vdEnum;

VideoDeviceEnumerator::constIterator ite = std::find_if(vdEnum.begin
(),vdEnum.end(),"nom du périphérique");

Enfin, vous voyez de quoi je parle ;)

Comment je peux faire ça?

J'ai entendu dire que c'était pas conseillé de dériver des classes de la
STL, donc j'ai bien peur de devoir réimplémenter tout ça :(

Merci d'avance...

Mike

10 réponses

1 2 3 4
Avatar
Pierre Barbier de Reuille
Michael wrote:
Bonsoir à tous,

j'ai les deux classes suivantes:

class VideoDevice
{
private:
std::string name;
public:
std::string & GetName() const { return name; }
};

class VideoDeviceEnumerator
{
public:
void FillList(TBox * box);
void getVideoDevice(const std::string & fooName, VideoDevice * vd);
};

Les deux fonctions de VideoDeviceEnumerator énumèrent en interne tous les
périphériques de capture vidéo disponibles sur le système... Et la fonction
FillList est tout ce qu'il y a de restrictif puisqu'elle ne fonctionne
qu'avec les objets de la VCL de Borland.

Je voudrais donc rendre cette classe identique à un itérateur, pour que
l'utilisateur puisse faire comme avec les conteneurs de la STL...

Un truc du genre:

VideoDeviceEnumerator vdEnum;

for (VideoDeviceEnumerator::constIterator ite = vdEnum.begin(); ite !=
vdEnum.end(); ++ite)
box->AddItem(ite->GetName());

Et pour obtenir un VideoDevice:

VideoDeviceEnumerator vdEnum;

VideoDeviceEnumerator::constIterator ite = std::find_if(vdEnum.begin
(),vdEnum.end(),"nom du périphérique");

Enfin, vous voyez de quoi je parle ;)

Comment je peux faire ça?

J'ai entendu dire que c'était pas conseillé de dériver des classes de la
STL, donc j'ai bien peur de devoir réimplémenter tout ça :(

Merci d'avance...

Mike


Bien, avec ce que tu donnes comme information y a pas grand chose à dire
sauf que ce que tu as deux solutions (AMA):
1 - le plus simple c'est tu remplis un container de la STL genre un
vector et ensuite "begin" et "end" renvoient simplement le "begin" et
"end" sur ce vecteur. Dans ton type il suffit de mettre dans la
définition de VideoDeviceEnumerator:

typedef vector<VideoDevice>::const_iterator const_iterator;

et le tour est joué ! Ensuite, si ça fait sens, tu peux rajouter
l'itérateur non-constant.

2 - plus compliqué, il faut que tu te fasse une classe qui se comporte
comme un itérateur (à toi de voir quel type d'itérateur, le plus simple
étant le "forward", cf
http://www.sgi.com/tech/stl/ForwardIterator.html). Disons que tu
appelles ta classe "VideoIterator" alors tu mets dans ta classe
VideoDeviceEnumerator:

typdef VideoIterator const_iterator;

puis tu définis begin et end pour renvoyer le bon VideoIterator.

Voilà, j'espère que ça aide, sinon faudrait donner plus d'info sur le
fonctionnement de VideoDeviceEnumerator.

Pierre

Avatar
Manuel Zaccaria
Michael a écrit:
Bonsoir à tous,


Bonsoir,

j'ai les deux classes suivantes:

class VideoDevice
{
private:
std::string name;
public:
std::string & GetName() const { return name; }


std::string const& GetName() const { return name; } // const is const
};


Si cette classe est une classe de base il est vivement conseillé de définir
un destructeur, soit virtuel et public(*), soit non virtuel et protégé(**).
De plus, comme son nom le fait penser, cette classe gère une ressource.
Dans ce cas mieux vaut désactiver le constructeur de copie et l'opérateur
d'affectation. En résumé :

class VideoDevice
{
private:
std::string name;

// no copy (sans implementation)
VideoDevice(VideoDevice const&);
VideoDevice& operator=(VideoDevice const&);

protected:
// ~VideoDevice() { /* ... */ } // (*) interdire la destruction par la base

public:
// virtual ~VideoDevice() { /* ... */ } // (**) autoriser (décommenter
selon)

std::string const& GetName() const { return name; }
};


class VideoDeviceEnumerator
{
public:
void FillList(TBox * box);
void getVideoDevice(const std::string & fooName, VideoDevice * vd);
};

Les deux fonctions de VideoDeviceEnumerator énumèrent en interne tous les
périphériques de capture vidéo disponibles sur le système... Et la
fonction
FillList est tout ce qu'il y a de restrictif puisqu'elle ne fonctionne
qu'avec les objets de la VCL de Borland.


Où sont les données membres ? On peut pas deviner comme ça si tout est
rangé dans un std::map<std::string, VideoDevice*> ou autre.
Disons que c'est un map pour simplifier.

Je voudrais donc rendre cette classe identique à un itérateur, pour que
l'utilisateur puisse faire comme avec les conteneurs de la STL...

Un truc du genre:


class VideoDeviceEnumerator
{
private:
// map de VideoDevice* si polymorphe
typedef std::map<std::string, VideoDevice*> map_type;

map_type devices;

public:

class const_iterator
{
private:
map_type::const_iterator iter;
public:
const_iterator(map_type::const_iterator iter):iter(iter){}
const_iterator& operator++() { ++iter; return *this; }
VideoDevice const* operator->() const { return iter->second; }
VideoDevice const& operator*() const { return *iter->second; }
};

const_iterator begin() { return devices.begin(); }
const_iterator end() { return devices.end(); }

// éventuellement, pour consulter/modifier ou remplir le map
VideoDevice* operator[](std::string const& name) { return
devices[name]; }
// etc...
};

// ...

VideoDeviceEnumerator vdEnum;
VideoDeviceEnumerator::const_iterator ite = vdEnum.begin();
VideoDeviceEnumerator::const_iterator end = vdEnum.end();
for(;ite != end; ++ite)
box->AddItem(ite->GetName());


Et pour obtenir un VideoDevice:



VideoDeviceEnumerator vdEnum;
VideoDeviceEnumerator::const_iterator ite = vdEnum.begin();
VideoDeviceEnumerator::const_iterator end = vdEnum.end();

VideoDeviceEnumerator::const_iterator ite = std::find(ite, end, "nom du
périphérique");

ou

VideoDevice* device = vdEnum["nom du périphérique"];


Comment je peux faire ça?


C'est une solution mais je n'ai pas testé le code (ça devrait tourner).


J'ai entendu dire que c'était pas conseillé de dériver des classes de la
STL, ...


Tout juste.


Manuel

Avatar
Manuel Zaccaria

Si cette classe est une classe de base il est vivement conseillé de
définir
un destructeur, soit virtuel et public(*), soit non virtuel et
protégé(**).
...


alors que

protected:
// ~VideoDevice() { /* ... */ } // (*) interdire la destruction par la
base

public:
// virtual ~VideoDevice() { /* ... */ } // (**) autoriser (décommenter
selon)


(*) <=> (**), à l'envers

[...]

VideoDeviceEnumerator::const_iterator ite = std::find(ite, end, "nom du
périphérique");


Impossible comme ça! il faut find_if avec un prédicat.

Bref, y'a sûrement d'autres erreurs... 'suis fatigué.


Manuel

Avatar
Stephane Wirtel
Bref, y'a sûrement d'autres erreurs... 'suis fatigué.
Peut-être fatigué, mais ça faisait un bout de temps que je me posais une question

concernant l'implémentation d'une surcouche iterator au dessus
des composants VCL de Borland.

Dans mon cas, j'ai besoin d'un iterator pour TList et TSQLQuery.

Le problème avec TList c'est qu'il s'agit un tableau qui contient des pointeurs
de type 'void', ce qui permet d'avoir une compatibilité avec Delphi.

Ce que je fais assez fréquemment, je travaille avec des STL containers et
quand il est nécessaire des passer des VCL "containers", je le génère
automatiquement.


Merci de cette info

Stef

Avatar
Michael
Si cette classe est une classe de base il est vivement conseillé de
définir un destructeur, soit virtuel et public(*), soit non virtuel et
protégé(**). De plus, comme son nom le fait penser, cette classe gère
une ressource. Dans ce cas mieux vaut désactiver le constructeur de
copie et l'opérateur d'affectation. En résumé :


Pas de soucis pour tout ça, j'ai fait court pour l'exemple...

Où sont les données membres ? On peut pas deviner comme ça si tout est
rangé dans un std::map<std::string, VideoDevice*> ou autre.
Disons que c'est un map pour simplifier.


Il n'y a pas de map

Par exemple, le code de GetVideoDevice

HRESULT FilterEnumerator::GetFilter(const AnsiString & filterName,
FilterBase * pComp) {
if (!pEm)
return E_POINTER;

pEm->Reset();

unsigned long cFetched = 0;
CComPtr<IMoniker> pMoniker;

while(SUCCEEDED(pEm->Next(1, &pMoniker, &cFetched)) && pMoniker)
{
CComPtr<IPropertyBag> pPropBag;
HRESULT hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void
**)&pPropBag); if (FAILED(hr))
return hr;

VARIANT varName;
VariantInit(&varName);

hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (FAILED(hr))
return hr;

AnsiString name = varName.bstrVal;
if (name == filterName)
{
CComPtr<IBaseFilter> pFilter;

hr = pMoniker->BindToObject(0, 0,
IID_IBaseFilter,(void**)&pFilter); if (FAILED(hr))
return hr;

pComp->SetFilter(pFilter,name);
return S_OK;
}

pMoniker.Release();
}

return E_FAIL;
}


Et là celui de la classe:

class FilterEnumerator
{
protected:
CComPtr<ICreateDevEnum> pSysDevEnum;
CComPtr<IEnumMoniker> pEm;
public:
FilterEnumerator();
//virtual ~DeviceEnumerator();

void Enumerate(TCustomListBox * box, const AnsiString & filterName
= ""); bool FilterExists(const AnsiString & filterName) const;
HRESULT GetFilter(const AnsiString & filterName, FilterBase *
pComp); bool IsFilterInCateg() const;
};

Ce qu'il faut faire je pense, c'est intégrer cette ligne dans la détection
du .end() et de l'opérateur ++

while(SUCCEEDED(pEm->Next(1, &pMoniker, &cFetched)) && pMoniker)

Je vais essayer de me débrouiller, on verra bien...

J'espérer pouvoir me débrouiller avec la STL...

Avatar
Manuel Zaccaria
Michael a écrit:

Disons que c'est un map pour simplifier.


Il n'y a pas de map

Par exemple, le code de GetVideoDevice


[snip code]

Ce qu'il faut faire je pense, c'est intégrer cette ligne dans la détection
du .end() et de l'opérateur ++

while(SUCCEEDED(pEm->Next(1, &pMoniker, &cFetched)) && pMoniker)


Presque ça. Je ne suis pas d'accord pour .end() par contre.


Je vais essayer de me débrouiller, on verra bien...

J'espérer pouvoir me débrouiller avec la STL...


Pardon si c'est trop H.S.

Le sujet est spécifique COM/Win32 mais je répond car le but est de
créer une façade (adaptateur) vers la STL.

La fonction GetVideoDevice fait trop de travail pour en faire un iterateur.
Il vaut mieux séparer l'itération de l'extraction des attributs.

Je ferais un iterateur sur les monikers (réutilisable du coup) et quelques
fonctions libres et enfin une classe exception :


/////////////////////////////////////

// la classe exception pour COM
// ou bien une autre... peu importe.

class com_exception : public exception
{
private:
HRESULT hr;
public:
com_exception(HRESULT hr):hr(hr){}
HRESULT result() const { return hr; }
};

/////////////////////////////////////

// la classe iterateur de monikers

class moniker_iterator
{
private:
CComPtr<IEnumMoniker> pEm;
CComPtr<IMoniker> pMoniker;
public:
moniker_iterator(CComPtr<IEnumMoniker> const& pem) // begin
: pEm(pem) {
if(pEm) {
HRESULT hr = pem->Reset(&pEm);
if(FAILED(hr))
throw com_exception(hr);

operator++(); // premier élément
}
}
moniker_iterator(moniker_iterator const& it) { // copie
if(it.pEm) {
HRESULT hr = it.pEm->Clone(&pEm);
if(FAILED(hr))
throw com_exception(hr);
}
}
moniker_iterator() {} // end

~moniker_iterator(){}

bool operator!=() const { return !operator==(it); }

bool operator==(moniker_iterator const& it) const {
return pMoniker == it.pMoniker;
}

moniker_iterator& operator++() {
if(!pEM)
throw com_exception(E_POINTER);

unsigned long cFetched;
IMoniker* pNewMoniker;
HRESULT hr = pEm->Next(1, &pNewMoniker, &cFetched);

// Tout va bien! mise à jour et retour.
pMoniker = SUCCEEDED(hr) ? pNewMoniker : 0;
return *this;
}

IMoniker* operator->() {
if(!pEM || !pMoniker)
throw com_exception(E_POINTER);

return pMoniker;
}
IMoniker& operator*() {
if(!pEM || !pMoniker)
throw com_exception(E_POINTER);

return *pMoniker;
}
};

/////////////////////////////////////

// les fonctions libres

AnsiString FriendlyName(IMoniker& moniker)
{
CComPtr<IPropertyBag> pPropBag;

CComVariant varName;
HRESULT hr;
if(FAILED(hr = moniker.BindToStorage(0, 0, IID_IPropertyBag,
(void**)&pPropBag))
|| FAILED(hr = pPropBag->Read(L"FriendlyName", &varName, 0)))
throw com_exception(hr);

return varName.bstrVal;
}

void BindToFilter(IMoniker& moniker, FilterBase& comp, AnsiString const&
name)
{
CComPtr<IBaseFilter> pFilter;
HRESULT hr = moniker.BindToObject(0, 0,
IID_IBaseFilter,(void**)&pFilter);
if (FAILED(hr))
throw com_exception(hr);

comp.SetFilter(pFilter, name);
}

etc...

/////////////////////////////////////

// exemple

AnsiString filterName = "MyFilter";
moniker_iterator begin(pem), end;

while((begin != end) && (FriendlyName(*begin) != filterName))
++begin;

if(begin != end)
BindToFilter(*begin, comp[i++], filterName);

/////////////////////////////////////

Normalement je compile et j'enlève les bugs... c'est pas testé donc.


Manuel


Avatar
Sylvain
Manuel Zaccaria wrote on 22/07/2006 18:38:

class moniker_iterator {
public:
moniker_iterator(CComPtr<IEnumMoniker> const& pem) .. {
...
throw com_exception(hr);
...
}
moniker_iterator(moniker_iterator const& it){
...
throw com_exception(hr);
}


est-ce les évolutions en cours de la norme prévoir d'obligeation de
déclaration des exceptions lévées (localement ou héritées) par une méthode ?

ie:
moniker_iterator(moniker_iterator const& it)
throw (com_exception)
{
...
}

est-ce que cela pourra s'accompagner d'une obligeation de catch de la
part du code appelant (s'il ne redéclare pas les mêmes throw) ?

Sylvain.

Avatar
Manuel Zaccaria
Sylvain a écrit:

est-ce les évolutions en cours de la norme prévoir d'obligeation de
déclaration des exceptions lévées (localement ou héritées) par une méthode
?

ie:
moniker_iterator(moniker_iterator const& it)
throw (com_exception)
{
...
}

est-ce que cela pourra s'accompagner d'une obligeation de catch de la part
du code appelant (s'il ne redéclare pas les mêmes throw) ?


Des spécifications d'exception comme en java ? J'espère bien que non.

Herb Sutter résume bien bien la situation :

"A Pragmatic Look at Exception Specifications"
http://www.gotw.ca/publications/mill22.htm

La morale est : "même pas throw()" ...discussion ?


Manuel

Avatar
Sylvain
Manuel Zaccaria wrote on 22/07/2006 23:35:

Des spécifications d'exception comme en java ? J'espère bien que non.

Herb Sutter résume bien bien la situation :


les 2 points sont liés ? Java fait une vérification à la compil.,
l'article ne parle que de vérification au runtime.

ma question portait sur un contrôle à la compilation.

Sylvain.

Avatar
Manuel Zaccaria
Sylvain a écrit:

Des spécifications d'exception comme en java ? J'espère bien que non.

Herb Sutter résume bien bien la situation :


les 2 points sont liés ? Java fait une vérification à la compil.,
l'article ne parle que de vérification au runtime.

ma question portait sur un contrôle à la compilation.


Si je me souviens bien, le fait de mettre des specifications
d'exception _force_ le compilateur a insérer des vérifications au
runtime (ce qui conduit à un abort() inconditionel si on les viole).


Manuel


1 2 3 4