Click here to Skip to main content
15,883,796 members
Articles / Programming Languages / C++

AList – A C++ Associative Array

Rate me:
Please Sign up or sign in to vote.
3.95/5 (7 votes)
2 Nov 2011CPOL4 min read 47.3K   446   17   20
An implementation of an assosiative array (a dictionary) in the standard C++ way.

Introduction

This article is about the standard C++ implementation of the dictionary type object. These special objects exist in other programming languages like Java, C#, and scripting languages like JavaScript, PHP, Lua, etc. There is a standard implementation of the dictionary object inside the STL library, called std::map. Another implementation can be found here on the CodeProject, but using the STL std::vector class. The implementation shown in this article does not use STL at all. All classes are written in the standard C++ language, and also some useful extensions have been added.

Background

Dictionaries as data types are useful as they keep different types of values inside a basic container. Some technologies introduce this type of objects as basic ones, like in Adobe's PDF specification. All objects inside the PDF document are dictionary objects. As an example, you could have different data describing the Person entity like name, age, birth date, other Person entities that are in relation to the parent Person element, etc. XML is useful to collect these information as there can be integers, floats, strings, booleans, or other complex data types. But working with XML in C++ might not be as easy as working with, let's say, arrays.

So how is information stored inside a dictionary? It uses key-value pairs to store data. The key is a unique identifier to the data. Please see the following link for a general discussion considering associative arrays or, so called, dictionaries: http://en.wikipedia.org/wiki/Associative_array.

JavaScript example of dictionary object

An object in JavaScript can be declared as a dictionary-type object using the Array or Object class, see below:

JavaScript
var person = new Array();
person.name = "Johny";
person.age = 32;
person.male = true;
person.boss = new Object();
person.boss["name"] = "Anna";
person.boss["age"] = 41;
person.boss["male"] = false;

So, this basically maps to the following XML segment:

XML
<person>
    <name>Jonny</name>
    <age>32</age>
    <male>true</male>
    <boss>
        <name>Anna</name>
        <age>41</age>
        <male>false</male>
    </boss>
</person>

It is very interesting to see what STL has to offer when it comes to the C++ language.

STL std::map as an example of a C++ implementation of dictionary

This is a declaration of the template class std::map:

C++
map <key_type, value_type [, comparing_option [, memory_allocator] ] > map_name

It can be used like this:

C++
std::map<std::string, std::string> person;
person["name"] = "Johny";
person["age"] = "32";
person["male"] = "true";

Then we come to the part where we must add another person object, but as you can see, with the current declaration of the person class, it is impossible. It is defined to use the std::string data type as the key, which is OK. But it also takes a std::string data type as the value, and that is not what we need, right?

How do we overcome this issue? Well, with the std::map class - no way out from here. It implements the dictionary type but only for single-level hierarchy objects. Also, values are of the same data type, and this could be an issue.

AList class as a simple alternative

I was thinking about a JavaScript implementation of dictionary objects when I started to work on this issue. Below, I am giving the full implementation of the AList class, since it is not too long:

C++
#ifndef ALIST_H_INCLUDED
#define ALIST_H_INCLUDED

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

using namespace std;

typedef enum __ItemValueType
{
    IVT_NULL = 0,
    IVT_BOOL = 1,
    IVT_INT = 2,
    IVT_FLOAT = 3,
    IVT_DOUBLE = 4,
    IVT_STRING = 5,
    IVT_LIST = 6,
    IVT_OBJECT = 7

} _ItemValueType;

class _Generic
{
    public:
        _Generic()
        {
        }

        virtual ~_Generic()
        {
        }

    protected:
        virtual void Destroy() = 0;

};

typedef struct __ItemValueInfo
{
    int index;
    char* name;
    _ItemValueType type;
    union
    {
        bool bValue;
        int iValue;
        float fValue;
        double eValue;
        char* sValue;
        _Generic* aValue;
        void* oValue;
    };

} _ItemValueInfo, *_lpItemValueInfo;

class ItemInfo : public _Generic
{
    public:
        ItemInfo()
        {
            memset(&m_value, 0, sizeof(_ItemValueInfo));
            m_value.type = IVT_NULL;
            m_value.index = -1;
        }

        ItemInfo(ItemInfo& ref)
        {
            m_value.type = ref.get_type();
            switch (m_value.type)
            {
                case IVT_BOOL:
                {
                    m_value.bValue = ref.get_value().bValue;
                }
                break;

                case IVT_INT:
                {
                    m_value.iValue = ref.get_value().iValue;
                }
                break;

                case IVT_FLOAT:
                {
                    m_value.fValue = ref.get_value().fValue;
                }
                break;

                case IVT_DOUBLE:
                {
                    m_value.eValue = ref.get_value().eValue;
                }
                break;

                case IVT_STRING:
                {
                    char* sValue = ref.get_value().sValue;
                    int len = strlen(sValue) + 1;
                    m_value.sValue = (char*)malloc(len);
                    strncpy(m_value.sValue, sValue, len-1);
                    m_value.sValue[len-1] = '\0';
                }
                break;

                case IVT_LIST:
                {
                    m_value.aValue = ref.get_value().aValue;
                }
                break;

                case IVT_OBJECT:
                {
                    m_value.oValue = ref.get_value().oValue;
                }
                break;

                default:
                {
                    memset(&m_value, 0, sizeof(_ItemValueInfo));
                    m_value.type = IVT_NULL;
                }
                break;
            }
        }

        virtual ~ItemInfo()
        {
            free(m_value.name);

            Destroy();
        }

        inline ItemInfo& operator = (ItemInfo& ref)
        {
            Destroy();

            m_value.type = ref.get_type();
            switch (m_value.type)
            {
                case IVT_BOOL:
                {
                    m_value.bValue = ref.get_value().bValue;
                }
                break;

                case IVT_INT:
                {
                    m_value.iValue = ref.get_value().iValue;
                }
                break;

                case IVT_FLOAT:
                {
                    m_value.fValue = ref.get_value().fValue;
                }
                break;

                case IVT_DOUBLE:
                {
                    m_value.eValue = ref.get_value().eValue;
                }
                break;

                case IVT_STRING:
                {
                    char* sValue = ref.get_value().sValue;
                    int len = strlen(sValue) + 1;
                    m_value.sValue = (char*)malloc(len);
                    strncpy(m_value.sValue, sValue, len-1);
                    m_value.sValue[len-1] = '\0';
                }
                break;

                case IVT_LIST:
                {
                    m_value.aValue = ref.get_value().aValue;
                }
                break;

                case IVT_OBJECT:
                {
                    m_value.oValue = ref.get_value().oValue;
                }
                break;

                default:
                {
                    memset(&m_value, 0, sizeof(_ItemValueInfo));
                    m_value.type = IVT_NULL;
                }
                break;
            }

            return (*this);
        }

        inline ItemInfo& operator = (const bool& ref)
        {
            Destroy();

            m_value.type = IVT_BOOL;
            m_value.bValue = ref;

            return (*this);
        }

        inline ItemInfo& operator = (const int& ref)
        {
            Destroy();

            m_value.type = IVT_INT;
            m_value.iValue = ref;

            return (*this);
        }

        inline ItemInfo& operator = (const float& ref)
        {
            Destroy();

            m_value.type = IVT_FLOAT;
            m_value.fValue = ref;

            return (*this);
        }

        inline ItemInfo& operator = (const double& ref)
        {
            Destroy();

            m_value.type = IVT_DOUBLE;
            m_value.eValue = ref;

            return (*this);
        }

        inline ItemInfo& operator = (const char* ref)
        {
            Destroy();

            m_value.type = IVT_STRING;
            int len = strlen(ref) + 1;
            m_value.sValue = (char*)malloc(len);
            strncpy(m_value.sValue, ref, len-1);
            m_value.sValue[len-1] = '\0';

            return (*this);
        }

        inline ItemInfo& operator = (_Generic* ref)
        {
            Destroy();

            m_value.type = IVT_LIST;
            m_value.aValue = ref;

            return (*this);
        }

        inline ItemInfo& operator = (void* ref)
        {
            Destroy();

            m_value.type = IVT_OBJECT;
            m_value.oValue = ref;

            return (*this);
        }

        friend inline ostream& operator << (ostream &stream, ItemInfo& ref)
        {
            switch (ref.get_type())
            {
                case IVT_BOOL:
                {
                    stream << ref.get_value().bValue;
                }
                break;

                case IVT_INT:
                {
                    stream << ref.get_value().iValue;
                }
                break;

                case IVT_FLOAT:
                {
                    stream << ref.get_value().fValue;
                }
                break;

                case IVT_DOUBLE:
                {
                    stream << ref.get_value().eValue;
                }
                break;

                case IVT_STRING:
                {
                    stream << ref.get_value().sValue;
                }
                break;

                case IVT_LIST:
                {
                    stream << "[List]";
                }
                break;

                case IVT_OBJECT:
                {
                    stream << "[Object]";
                }
                break;

                default:
                {
                }
                break;
            }

            return stream;
        }

        ItemInfo& operator [] (const int index);
        ItemInfo& operator [] (const char* name);

        inline int get_index()
        {
            return (m_value.index);
        }

        inline void set_index(const int index)
        {
            m_value.index = index;
        }

        inline char* get_name()
        {
            return (m_value.name);
        }

        inline void set_name(const char* name)
        {
            int len = strlen(name) + 1;
            m_value.name = (char*)realloc(m_value.name, len*sizeof(char));
            strcpy(m_value.name, name);
            m_value.name[len-1] = '\0';
        }

        inline _ItemValueType get_type()
        {
            return (m_value.type);
        }

        inline _ItemValueInfo get_value()
        {
            return (m_value);
        }

    protected:
        virtual void Destroy()
        {
            if (m_value.type == IVT_STRING)
            {
                free(m_value.sValue);
            }

            if (m_value.type == IVT_LIST)
            {
                delete (m_value.aValue);
            }

            m_value.type = IVT_NULL;
        }

    private:
        _ItemValueInfo m_value;

};

class AList : public _Generic
{
    public:
        AList()
        {
            m_items = NULL;
            m_size = 0;
        }

        virtual ~AList()
        {
            empty();
        }

        inline void empty()
        {
            Destroy();
        }

        inline ItemInfo& operator[] (const int index)
        {
            ItemInfo* pItem = find_item(index);
            if (pItem != NULL)
            {
                return (*pItem);
            }
            else
            {
                m_size++;
                m_items = (ItemInfo**)realloc(m_items, m_size*sizeof(ItemInfo*));
                m_items[m_size-1] = new ItemInfo();
                m_items[m_size-1]->set_index(index);
                return (*(m_items[m_size-1]));
            }
        }

        inline ItemInfo& operator[] (const char* name)
        {
            ItemInfo* pItem = find_item(name);
            if (pItem != NULL)
            {
                return (*pItem);
            }
            else
            {
                m_size++;
                m_items = (ItemInfo**)realloc(m_items, m_size*sizeof(ItemInfo*));
                m_items[m_size-1] = new ItemInfo();
                m_items[m_size-1]->set_name(name);
                return (*(m_items[m_size-1]));
            }
        }

        friend inline ostream& operator << (ostream &stream, AList& ref)
        {
            stream << "[List]";

            return stream;
        }

        inline int get_size()
        {
            return m_size;
        }

        inline ItemInfo** get_items()
        {
            return m_items;
        }

        inline ItemInfo* find_item(const int index)
        {
            int iIndex = -1;
            for (int i=0; i<m_size; i++)
            {
                if (m_items[i]->get_index() == index)
                {
                    iIndex = i;
                    break;
                }
            }

            if (iIndex != -1)
            {
                return (m_items[iIndex]);
            }
            else
            {
                return NULL;
            }
        }

        inline ItemInfo* find_item(const char* name)
        {
            int iIndex = -1;
            for (int i=0; i<m_size; i++)
            {
                if (strcmp(m_items[i]->get_name(), name) == 0)
                {
                    iIndex = i;
                    break;
                }
            }

            if (iIndex != -1)
            {
                return (m_items[iIndex]);
            }
            else
            {
                return NULL;
            }
        }

protected:
        virtual void Destroy()
        {
            for (int i=0; i<m_size; i++)
            {
                delete m_items[i];
            }
            free(m_items);
            m_size = 0;
        }

    private:
        ItemInfo** m_items;
        int m_size;

};

ItemInfo& ItemInfo::operator[] (const int index)
{
    return (((AList&)(*m_value.aValue))[index]);
}

ItemInfo& ItemInfo::operator[] (const char* name)
{
    return (((AList&)(*m_value.aValue))[name]);
}

#endif // ALIST_H_INCLUDED

First, there is a simple enum definition which covers some basic data types like booleans, numbers, and strings, etc. There are also two additional data types: one for AList objects (important for embedded objects), and one for a general class object of any data type (user specific). After that, an abstract _Generic class has been defined. Next, the general item field has been declared that keeps values of different types. It has two important members that can be accessed by an index and a name.

Now we come to the definition of the ItemInfo class which represents an entry in our dictionary collection. It has defined operators to accept different data types, and also the output stream operator so you can print the value on the standard console output.

The container class that is defined next is the dictionary AList class. It also has standard operators defined to work with it like you do with the simple array (like in JavaScript). This class allows you to put any basic type inside the container, and also another AList object, or an object of any type. This is enough to support the example from the beginning of this article.

How to use it

To use it, you need to create objects of the AList class, and join them together, see below:

C++
AList person;
person["name"] = "John";
person["age"] = 32;
person["male"] = true;
person["boss"] = new AList();
person["boss"]["name"] = "Anna";
person["boss"]["age"] = 41;
person["boss"]["male"] = false;

To print it on the console output, see below:

C++
cout << person["name"] << endl;
cout << person["age"] << endl;
cout << person["male"] << endl;
cout << person["boss"]["name"] << endl;
cout << person["boss"]["age"] << endl;
cout << person["boss"]["male"] << endl;

And, here is the output:

AList output

AList and ItemInfo class interface

Listed below is a short list of all the AList class public member functions:

C++
// Clears the AList array
inline void empty();
// Gets the element using its index
inline ItemInfo& operator[] (const int index);
// Gets the element using its name
inline ItemInfo& operator[] (const char* name);
// Outputs AList array on the console window
friend inline ostream& operator << (ostream &stream, AList& ref);
// Gets the size of the AList array
inline int get_size();
// Gets the elements collection
inline ItemInfo** get_items();

And here is the public interface to the ItemInfo class:

C++
// Assignment operator
inline ItemInfo& operator = (ItemInfo& ref);
// Assignment operator
inline ItemInfo& operator = (const bool& ref);
// Assignment operator
inline ItemInfo& operator = (const int& ref);
// Assignment operator
inline ItemInfo& operator = (const float& ref);
// Assignment operator
inline ItemInfo& operator = (const double& ref);
// Assignment operator
inline ItemInfo& operator = (const char* ref);
// Assignment operator
inline ItemInfo& operator = (_Generic* ref);
// Assignment operator
inline ItemInfo& operator = (void* ref);
// Outputs the element on the console window
friend inline ostream& operator << (ostream &stream, ItemInfo& ref);
// Gets the element using its index
ItemInfo& operator [] (const int index);
// Gets the element using its name
ItemInfo& operator [] (const char* name);
// Gets the index of the element
inline int get_index();
// Sets the index of the element
inline void set_index(const int index);
// Gets the name of the element
inline char* get_name();
// Sets the name of the element
inline void set_name(const char* name);
// Gets the type of the element
inline _ItemValueType get_type();
// Gets the value of the element
inline _ItemValueInfo get_value();

Collecting the correct value from an AList element

Here is the definition of the value for each element in the container:

C++
typedef struct __ItemValueInfo
{
    int index;
    char* name;
    _ItemValueType type;
    union
    {
        bool bValue;
        int iValue;
        float fValue;
        double eValue;
        char* sValue;
        _Generic* aValue;
        void* oValue;
    };

} _ItemValueInfo, *_lpItemValueInfo;

To get the original value from the element inside the container, do the following:

C++
// Gets the "name" as string
char* name = person["boss"]["name"].get_value().sValue;
// Gets the "age" as integer
int age = person["boss"]["age"].get_value().iValue;
// Gets the "male" as bool
bool male = person["boss"]["male"].get_value().bValue;
// Gets the "boss" subarray
AList& boss = (AList&)(*person["boss"].get_value().aValue);
// Gets "user-defined" object (if any)
MyObject* myObject = (MyObject*)(array["key"].get_value().oValue);

AList compared to the std::map STL class

The idea here is not to replace the std::map STL class by any means. The AList class presented in this article does not implement advanced data storage structures like hashtables, like std::map does, nor advanced searching through the elements of the collection. It just gives an option to the developer when his needs exceed the dictionary implementation of the std::map class, especially in multi-hierarchy problems like in XML files or PDF documents.

Points of interest

This was a very interesting subject I have been working on, and I hope developers will find it a useful place in their everyday projects.

History

  • AList version 1.0, November 2011.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Elektromehanika d.o.o. Nis
Serbia Serbia
He has a master degree in Computer Science at Faculty of Electronics in Nis (Serbia), and works as a C++/C# application developer for Windows platforms since 2001. He likes traveling, reading and meeting new people and cultures.

Comments and Discussions

 
GeneralMy vote of 1 Pin
pasztorpisti5-Sep-13 4:43
pasztorpisti5-Sep-13 4:43 
QuestionReplacement for std::map Pin
JeffBilkey8-Nov-11 14:39
JeffBilkey8-Nov-11 14:39 
AnswerRe: Replacement for std::map Pin
darkoman8-Nov-11 20:42
darkoman8-Nov-11 20:42 
Newsstd::map alternative Pin
darkoman3-Nov-11 0:02
darkoman3-Nov-11 0:02 
GeneralRe: std::map alternative Pin
Member 351467110-Nov-11 20:40
Member 351467110-Nov-11 20:40 
GeneralRe: std::map alternative Pin
Philip Beck16-Feb-15 4:07
Philip Beck16-Feb-15 4:07 
QuestionTry std::multimap Pin
steveb2-Nov-11 10:54
mvesteveb2-Nov-11 10:54 
GeneralMy vote of 4 Pin
Ramon F. Mendes2-Nov-11 8:53
Ramon F. Mendes2-Nov-11 8:53 
QuestionCouple of issues Pin
Jim Crafton2-Nov-11 6:47
Jim Crafton2-Nov-11 6:47 
AnswerRe: Couple of issues Pin
darkoman2-Nov-11 11:46
darkoman2-Nov-11 11:46 
GeneralRe: Couple of issues Pin
Hongjun Ge2-Nov-11 19:10
Hongjun Ge2-Nov-11 19:10 
AnswerRe: Couple of issues Pin
darkoman2-Nov-11 23:06
darkoman2-Nov-11 23:06 
Suggestionassosiative => associative Pin
Pascal Ganaye2-Nov-11 6:20
Pascal Ganaye2-Nov-11 6:20 
AnswerRe: assosiative => associative Pin
darkoman2-Nov-11 11:34
darkoman2-Nov-11 11:34 
AnswerRe: assosiative => associative Pin
JCrane23-Nov-11 2:39
JCrane23-Nov-11 2:39 
GeneralRe: assosiative => associative Pin
Pascal Ganaye3-Nov-11 8:15
Pascal Ganaye3-Nov-11 8:15 
QuestionWhy not just use std::map &lt;std::string, boost::any&gt; ? Pin
mclow2-Nov-11 5:44
mclow2-Nov-11 5:44 
AnswerRe: Why not just use std::map <std::string, boost::any> ? Pin
damnedyankee2-Nov-11 6:12
damnedyankee2-Nov-11 6:12 
Not Invented Here Syndrome

It seems people would rather spend hours (or days or even weeks) debugging extremely obscure edge cases in their own homegrown code than use a common library function with thousands of man-hours of debugging already in place.
AnswerRe: Why not just use std::map ? Pin
darkoman2-Nov-11 11:30
darkoman2-Nov-11 11:30 
AnswerRe: Why not just use std::map <std::string, boost::any> ? Pin
JeffBilkey8-Nov-11 14:24
JeffBilkey8-Nov-11 14:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.