CopperSpice API  1.7.2
Container Classes

CopperSpice provides several general purpose container classes which use templates to define the data types of the stored elements. QVector<QString> is an example of a container that stores strings. To store a set of integers without duplicates you can use QSet<int>. Containers can be used 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 which is stored in a container should be default constructible and copy constructible. If the type T has a default constructor and a copy constructor, then the T will work for every method of all containers.

If you use a T which is missing one of these constructors, calls to some of the container methods will not compile.

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 (QWidget, QDialog, QTimer, etc.) are not copy constructible and therefore should never be stored in a container. You can however use a pointer to a QObject or any subclass as the type T.

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

class Employee
{
public:
Employee() = default;
Employee(const Employee &other) {
// some code
}
private:
QString myName;
QDate myDateOfBirth;
};

Constructors

If a default constructor, copy constructor, or an assignment operator is not defined for a given class or struct, the compiler will provide a default implementation, unless the class or struct explicitly states these should not exist.

The implementation of the default constructor is to create the object with an empty initial value. The default implementation of the copy constructor will make a copy of each member.

In this example, although there is no user defined constructors or assignment operators, this data type can be used as the T for any container.

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

Default Constructed Elements

For most data types the default constructor will be called when creating a new object.

QVector<int> value1; // value1 is an empty vector with no elements

If the type T is a QString, then an empty string will created. For a QDate the default is a null date, which is considered invalid. For primitive types like int and double C++ does not specify any default initialization so these will be default initialized to an undefined value. A pointer type is also default initialized to an undefined value.

QString value2; // value2 is an empty string
QDate value3; // value3 is a null date
int value4; // value4 is undefined
QWidget *value5; // value5 is undefined

For containers, where elements are created in the declaration, the constructed object will have elements which are value initialized. With a user defined type T of QString, the initialization is the same as declaring a local variable as shown above. Default initialized and value initialized will yield the same result

QVector<QString> value6(50); // 50 elements where one has an initial value of an empty string

In contrast, for a container with primitive types like int or double, the value initialization is 0. For pointer types, the initial value is a nullptr.

QVector<int> value7(50); // 50 elements, each has an initial value of 0
QVector<QWidget *> value8(50); // 50 elements, each has an initial value of nullptr
  • Containers automatically initialize their elements with value initialized objects
  • QMap::value() method returns a value initialized object value when the specified key is not found in the map

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