J'essayais de résoudre le problème de pouvoir itérer sur plusieurs tableaux de texte différents, tous stockés dans une base de données résidente en mémoire qui est grande struct
.
Les éléments suivants ont été élaborés à l'aide de Visual Studio 2017 Community Edition sur une application de test MFC. J'inclus ceci à titre d'exemple, car cette publication était l'une des nombreuses que j'ai rencontrées et qui fournissaient de l'aide mais qui étaient encore insuffisantes pour mes besoins.
Le struct
contenant les données résidentes en mémoire ressemblait à quelque chose comme suit. J'ai supprimé la plupart des éléments par souci de concision et n'ai pas non plus inclus les définitions de préprocesseur utilisées (le SDK utilisé est pour C ainsi que C ++ et est ancien).
Ce qui m'intéressait, c'était d'avoir des itérateurs pour les différents WCHAR
tableaux bidimensionnels qui contenaient des chaînes de texte pour les mnémoniques.
typedef struct tagUNINTRAM {
// stuff deleted ...
WCHAR ParaTransMnemo[MAX_TRANSM_NO][PARA_TRANSMNEMO_LEN]; /* prog #20 */
WCHAR ParaLeadThru[MAX_LEAD_NO][PARA_LEADTHRU_LEN]; /* prog #21 */
WCHAR ParaReportName[MAX_REPO_NO][PARA_REPORTNAME_LEN]; /* prog #22 */
WCHAR ParaSpeMnemo[MAX_SPEM_NO][PARA_SPEMNEMO_LEN]; /* prog #23 */
WCHAR ParaPCIF[MAX_PCIF_SIZE]; /* prog #39 */
WCHAR ParaAdjMnemo[MAX_ADJM_NO][PARA_ADJMNEMO_LEN]; /* prog #46 */
WCHAR ParaPrtModi[MAX_PRTMODI_NO][PARA_PRTMODI_LEN]; /* prog #47 */
WCHAR ParaMajorDEPT[MAX_MDEPT_NO][PARA_MAJORDEPT_LEN]; /* prog #48 */
// ... stuff deleted
} UNINIRAM;
L'approche actuelle consiste à utiliser un modèle pour définir une classe proxy pour chacun des tableaux, puis à avoir une seule classe d'itérateur qui peut être utilisée pour itérer sur un tableau particulier en utilisant un objet proxy représentant le tableau.
Une copie des données résidentes en mémoire est stockée dans un objet qui gère la lecture et l'écriture des données résidentes en mémoire depuis / vers le disque. Cette classe, CFilePara
contient la classe proxy sur matrice ( MnemonicIteratorDimSize
et la sous - classe à partir de laquelle elle est dérivée est, MnemonicIteratorDimSizeBase
) et la classe d'itération, MnemonicIterator
.
L'objet proxy créé est attaché à un objet itérateur qui accède aux informations nécessaires via une interface décrite par une classe de base à partir de laquelle toutes les classes proxy sont dérivées. Le résultat est d'avoir un seul type de classe d'itérateur qui peut être utilisé avec plusieurs classes proxy différentes car les différentes classes proxy exposent toutes la même interface, l'interface de la classe de base proxy.
La première chose a été de créer un ensemble d'identifiants qui seraient fournis à une fabrique de classes pour générer l'objet proxy spécifique pour ce type de mnémonique. Ces identifiants sont utilisés dans le cadre de l'interface utilisateur pour identifier les données d'approvisionnement particulières que l'utilisateur souhaite voir et éventuellement modifier.
const static DWORD_PTR dwId_TransactionMnemonic = 1;
const static DWORD_PTR dwId_ReportMnemonic = 2;
const static DWORD_PTR dwId_SpecialMnemonic = 3;
const static DWORD_PTR dwId_LeadThroughMnemonic = 4;
La classe proxy
La classe proxy basée sur un modèle et sa classe de base sont les suivantes. J'avais besoin de prendre en charge plusieurs types de wchar_t
tableaux de chaînes de texte. Les tableaux bidimensionnels avaient différents nombres de mnémoniques, selon le type (but) de la mnémonique et les différents types de mnémoniques étaient de longueurs maximales différentes, variant entre cinq caractères de texte et vingt caractères de texte. Les modèles de la classe proxy dérivée correspondaient naturellement au modèle nécessitant le nombre maximal de caractères dans chaque mnémonique. Une fois l'objet proxy créé, nous utilisons ensuite la SetRange()
méthode pour spécifier le tableau mnémonique réel et sa plage.
// proxy object which represents a particular subsection of the
// memory resident database each of which is an array of wchar_t
// text arrays though the number of array elements may vary.
class MnemonicIteratorDimSizeBase
{
DWORD_PTR m_Type;
public:
MnemonicIteratorDimSizeBase(DWORD_PTR x) { }
virtual ~MnemonicIteratorDimSizeBase() { }
virtual wchar_t *begin() = 0;
virtual wchar_t *end() = 0;
virtual wchar_t *get(int i) = 0;
virtual int ItemSize() = 0;
virtual int ItemCount() = 0;
virtual DWORD_PTR ItemType() { return m_Type; }
};
template <size_t sDimSize>
class MnemonicIteratorDimSize : public MnemonicIteratorDimSizeBase
{
wchar_t (*m_begin)[sDimSize];
wchar_t (*m_end)[sDimSize];
public:
MnemonicIteratorDimSize(DWORD_PTR x) : MnemonicIteratorDimSizeBase(x), m_begin(0), m_end(0) { }
virtual ~MnemonicIteratorDimSize() { }
virtual wchar_t *begin() { return m_begin[0]; }
virtual wchar_t *end() { return m_end[0]; }
virtual wchar_t *get(int i) { return m_begin[i]; }
virtual int ItemSize() { return sDimSize; }
virtual int ItemCount() { return m_end - m_begin; }
void SetRange(wchar_t (*begin)[sDimSize], wchar_t (*end)[sDimSize]) {
m_begin = begin; m_end = end;
}
};
La classe des itérateurs
La classe d'itérateur elle-même est la suivante. Cette classe fournit simplement une fonctionnalité d'itérateur direct de base, ce qui est tout ce qui est nécessaire pour le moment. Cependant, je m'attends à ce que cela change ou soit étendu lorsque j'en ai besoin de quelque chose de plus.
class MnemonicIterator
{
private:
MnemonicIteratorDimSizeBase *m_p; // we do not own this pointer. we just use it to access current item.
int m_index; // zero based index of item.
wchar_t *m_item; // value to be returned.
public:
MnemonicIterator(MnemonicIteratorDimSizeBase *p) : m_p(p) { }
~MnemonicIterator() { }
// a ranged for needs begin() and end() to determine the range.
// the range is up to but not including what end() returns.
MnemonicIterator & begin() { m_item = m_p->get(m_index = 0); return *this; } // begining of range of values for ranged for. first item
MnemonicIterator & end() { m_item = m_p->get(m_index = m_p->ItemCount()); return *this; } // end of range of values for ranged for. item after last item.
MnemonicIterator & operator ++ () { m_item = m_p->get(++m_index); return *this; } // prefix increment, ++p
MnemonicIterator & operator ++ (int i) { m_item = m_p->get(m_index++); return *this; } // postfix increment, p++
bool operator != (MnemonicIterator &p) { return **this != *p; } // minimum logical operator is not equal to
wchar_t * operator *() const { return m_item; } // dereference iterator to get what is pointed to
};
La fabrique d'objets proxy détermine quel objet à créer en fonction de l'identifiant mnémonique. L'objet proxy est créé et le pointeur renvoyé est le type de classe de base standard afin d'avoir une interface uniforme quelle que soit la section mnémonique à laquelle on accède. La SetRange()
méthode est utilisée pour spécifier à l'objet proxy les éléments de tableau spécifiques que le proxy représente et la plage des éléments de tableau.
CFilePara::MnemonicIteratorDimSizeBase * CFilePara::MakeIterator(DWORD_PTR x)
{
CFilePara::MnemonicIteratorDimSizeBase *mi = nullptr;
switch (x) {
case dwId_TransactionMnemonic:
{
CFilePara::MnemonicIteratorDimSize<PARA_TRANSMNEMO_LEN> *mk = new CFilePara::MnemonicIteratorDimSize<PARA_TRANSMNEMO_LEN>(x);
mk->SetRange(&m_Para.ParaTransMnemo[0], &m_Para.ParaTransMnemo[MAX_TRANSM_NO]);
mi = mk;
}
break;
case dwId_ReportMnemonic:
{
CFilePara::MnemonicIteratorDimSize<PARA_REPORTNAME_LEN> *mk = new CFilePara::MnemonicIteratorDimSize<PARA_REPORTNAME_LEN>(x);
mk->SetRange(&m_Para.ParaReportName[0], &m_Para.ParaReportName[MAX_REPO_NO]);
mi = mk;
}
break;
case dwId_SpecialMnemonic:
{
CFilePara::MnemonicIteratorDimSize<PARA_SPEMNEMO_LEN> *mk = new CFilePara::MnemonicIteratorDimSize<PARA_SPEMNEMO_LEN>(x);
mk->SetRange(&m_Para.ParaSpeMnemo[0], &m_Para.ParaSpeMnemo[MAX_SPEM_NO]);
mi = mk;
}
break;
case dwId_LeadThroughMnemonic:
{
CFilePara::MnemonicIteratorDimSize<PARA_LEADTHRU_LEN> *mk = new CFilePara::MnemonicIteratorDimSize<PARA_LEADTHRU_LEN>(x);
mk->SetRange(&m_Para.ParaLeadThru[0], &m_Para.ParaLeadThru[MAX_LEAD_NO]);
mi = mk;
}
break;
}
return mi;
}
Utilisation de la classe proxy et de l'itérateur
La classe proxy et son itérateur sont utilisés comme indiqué dans la boucle suivante pour remplir un CListCtrl
objet avec une liste de mnémoniques. J'utilise de std::unique_ptr
sorte que lorsque la classe proxy n'est plus nécessaire et qu'elle std::unique_ptr
sort du cadre, la mémoire sera nettoyée.
Ce que fait ce code source, c'est de créer un objet proxy pour le tableau dans struct
lequel correspond à l'identifiant mnémonique spécifié. Il crée ensuite un itérateur pour cet objet, utilise une distance for
pour remplir le CListCtrl
contrôle, puis nettoie. Ce sont toutes des wchar_t
chaînes de texte brutes qui peuvent être exactement le nombre d'éléments de tableau, nous copions donc la chaîne dans un tampon temporaire afin de nous assurer que le texte se termine par zéro.
std::unique_ptr<CFilePara::MnemonicIteratorDimSizeBase> pObj(pFile->MakeIterator(m_IteratorType));
CFilePara::MnemonicIterator pIter(pObj.get()); // provide the raw pointer to the iterator who doesn't own it.
int i = 0; // CListCtrl index for zero based position to insert mnemonic.
for (auto x : pIter)
{
WCHAR szText[32] = { 0 }; // Temporary buffer.
wcsncpy_s(szText, 32, x, pObj->ItemSize());
m_mnemonicList.InsertItem(i, szText); i++;
}