CopperSpice API  1.7.4
Container Classes

CopperSpice provides several container classes which use templates to define the data types of the stored elements. QVector<QString> is an example of a container which stores strings. To store a set of integers without duplicates use QSet<int>. Containers can be used as a value in another container. For example, QMap<QString, QList<int>> defines a QMap where the key is a QString and the value is a QList<int>.

Overview

The CopperSpice sequential containers and associative containers are shown in the table below. The container classes are implemented using the C++ standard library (STL). The API for our containers has been extended to support the full STL style syntax while maintaining all of the existing API.

Class Based On Description
QList<T> std::deque Stores a list of elements of a given data type which can be accessed by an index
QLinkedList<T> std::list Better performance than QList when inserting in the middle of a large list,
iterators pointing to an item in a QLinkedList remain valid as long as the item exists
QStringList Inherits from QList<QString> Provides several methods which only pertain to strings
QQueue<T> Implemented using QList Provides "first in, first out" (FIFO) semantics
QStack<T> Implemented using QVector Provides "last in, first out" (LIFO) semantics
QSet<T> std::unordered_set Similar to QHash except only storing keys, duplicates not allowed, fast lookups
QFlatMap<K, V> Implemented using QVector Similar to QMap, uses less memory, good choice when the number of elements is small
QMap<K, V> std::map Stores a key, value pair, slower when the number of elements is large
QMultiMap<K, V> std::multimap Stores a key, value pair, duplicate keys are allowed
QHash<K, V> std::unordered_map Stores a key, value pair, significantly faster than QMap
QMultiHash<K, V> std::unordered_multimap Stores a key, value pair, duplicate keys are allowed
QVector<T> std::vector Stores elements in contiguous memory
  • When elements need to occupy adjacent memory locations to support a C style API, use QVector
  • Insert and erase at the end of a QVector is fast, whereas at the beginning or middle can be slow
  • Insert and erase at both ends of a QList are fast, whereas in the middle these operations may be slow
  • Elements in a QMap are sorted by key, elements in a QHash are in an arbitrary order
  • QHash provides faster lookups than a QMap or a QFlatMap
  • QHash class requires the type for key provide an operator==() method and a global qHash(key) function
  • QMap class requires the type for key provide an operator<() method

Constructors for type T

The values stored in a container should be default constructible and copy constructible. If the type T meets these requirements then every method in the container class can be invoked. If you use a T which does not support both of these constructors, calls to some of the container methods will not compile when invoked.

Basic data types such as int, double, pointer types, and built in types such as QString, QDate, and QTime are all valid as the T. Data types like QObject or any subclass like QWidget, QDialog, QTimer, are not copy constructible and therefore data of these types should never be stored in a container. As an alternative, a pointer to a QObject or any subclass can be stored in any CopperSpice container class.

The following is an example of a custom data type which has a default constructor and copy constructor. The Employee class can be used as the T for any container.

class Employee
{
public:
Employee() = default;
Employee(const Employee &other) = default;
Employee &operator=(const Employee &other) = default;
private:
QString m_name;
QDate m_dateOfBirth;
};

Constructors

If a default constructor, copy constructor, or an assignment operator is not declared the compiler will provide a default implementation when possible. The compiler generated implementation of a default constructor creates the object by calling the default constructor for the parent class. The default implementation of the copy constructor will make a copy of each class member.

In the following example no constructors or assignment constructors are declared so the compiler will generate the appropriate methods. This data type can be used as the T for any container.

struct Movie {
int id;
QString title;
QDate releaseDate;
};

Default Constructed Elements

When a new object is created or instantiated and there is no data passed to the constructor, a default constructor is typically called. For primitive types like int, double, pointer types, and char, C++ does not specify any default initialization so these types will be default initialized. However reading this value is undefined behavior. You should always initialize primitive types.

The same holds true for default initialized values stored in a container.

QVector<int> data_A(20); // 20 elements, each has an initial value of undefined
QVector<QWidget *> data_B(20); // 20 elements, each has an initial value of undefined

In the following example data_C will be default constructed with five elements. Each element will be a default constructed QString. The default constructor for QString creates a valid empty string.

QVector<QString> data_C(5);

If the T is a QDate the elements will be invalid since the default constructor for this class creates a null date which is considered invalid.

Reading and Writing to Containers

The CopperSpice containers provide operator<<() and operator>>() so data can be read and written using a QDataStream. This requires the type T stored in the container must also support the operator<<() and operator>>() functions.

The following is an example using "struct Movie" shown above.

QDataStream &operator<<(QDataStream &out, const Movie &movie) {
out << (qint32)movie.id << movie.title << movie.releaseDate;
return out;
}
QDataStream &operator>>(QDataStream &in, Movie &movie) {
qint32 id;
QDate date;
in >> id >> movie.title >> date;
movie.id = (int)id;
movie.releaseDate = date;
return in;
}

Range Based For

To iterate or navigate over every element of a container it is better to use a "range based for loop" whenever possible. The following example shows the correct syntax.

for ( range_declaration : range_expression )
  • range_declaration - declaration of a named variable
  • range_expression - container, expression, or function
    • any object which supports the begin() and end() methods or free functions
for (QString value : list) {
qDebug() << value;
}
Note
With containers like QMap and QHash only the "value" of the "key, value" pair is assigned to the range_declaration. This is very important if the loop needs to access both the key and value, in which case an iterator will be required.

Other Container Classes

There are four other special purpose container classes worth mentioning. They have STL style iterators and can be used with the C++ range based for loop syntax.

More about Containers