CopperSpice API  1.9.2
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 and no data is passed to the constructor, a default constructor is called if one is available. There are two ways to default construct an object.

Default Initialized

The object will be default initialized if no parameter list is passed. The following are are examples of default initialized objects.

int var1;
QString var2;

For primitive types like int, double, pointer types, and char, default initialization creates an uninitialized value. Reading a default initialized value of a primitive type is undefined behavior. Ensure all primitive types in your application are initialized to a defined value before reading.

Value Initialized

The other approach is called value initialized and this occurs when an empty parameter list is passed.

int var1 = int();
QString var2 = QString();

For primitive types like int, double, pointer types, and char, value initialization creates an value of zero or nullptr. Reading a value initialized value of a primitive type is valid.

Default Values

The following table shows the default value for the most common data types.

Data Type Default Initialized Value Initialized
char uninitialized \0
int, float, double uninitialized 0
pointer uninitialized nullptr
QDate invalid date invalid date
QString empty empty
QTime invalid time invalid time
QUrl empty empty
QUUid all zeros all zeros
Example 1

In the following example data1 will be constructed with twenty default initialized elements. Each element will be a uninitialized int. The elements for data2 will be uninitialized pointers.

QVector<int> data1(20);
Example 2

In the following example data3 will be constructed with five default initialized elements. Each element will contain a valid empty string.

If the T is a QDate the default initialized elements will be set to an invalid date by the default constructor for QDate.

QVector<QDate> data4(5);

Reading and Writing to Containers

The CopperSpice containers provide operator<<() and operator>>() function for reading and writing data 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".

struct Movie {
int id;
QString title;
QDate releaseDate;
};
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