С++: Типобезопасное перечисление (enum class) в unordered_map

Это статья о языке программирования C++.

Элементы обычного перечисление (enum) запросто могут применяться в качестве ключа в unordered_map. Следующий пример показывает, как сопоставить элемент перечисления с текстовым значением.

#include <string>
#include <iostream>
#include <unordered_map>

enum Color { Red, Blue, Green, };

int main()
{
    std::unordered_map<int, std::string> colors {
        {Red, "Red"},
        {Blue, "Blue"},
        {Green, "Green"},
    };
    
    std::cout << colors.at(Red) << std::endl;
}

Выполнить код.

Этот пример выведет в консоль слово «Red». Заметьте, что тип ключа — int.

Такое перечисление не типобезопасно и устарело. Язык C++11 предоставляет типобезопасные перечисления (enum class). У такого перечисления для доступа к элементам надо указывать имя перечисления.

Ниже приведен пример типобезопасного перечисления.

enum class Month
{
    January,
    February,
    March,
    April,
    May,
    June,
    July,
    August,
    September,
    October,
    November,
    December,
};

Чтобы получить доступ к элементу January, нужно написать Month::January.

Элементы такого перечисления можно использовать в аналогичном unordered_map, но только с явным привидением типа с помощью static_cast. Пример:

int main()
{
    std::unordered_map<int, std::string> colors{
        {static_cast<int>(Month::January), "January"},
        {static_cast<int>(Month::February), "February"},
    };
    
    std::cout << colors.at(static_cast<int>(Month::January)) << std::endl;
}

Использование static_cast делает код громоздким.

Код ниже показывает, как создать unordered_map, у которого в качестве ключа элементы типобезопасного перечисления, и где не требуется приведение типа.

#include <string>
#include <iostream>
#include <unordered_map>

struct EnumClassHash
{
    template <typename T>
    std::size_t operator()(T t) const
    {
        return static_cast<std::size_t>(t);
    }
};

enum class Month
{
    January,
    February,
    March,
    April,
    May,
    June,
    July,
    August,
    September,
    October,
    November,
    December,
};

int main()
{
    std::unordered_map<Month, std::string, EnumClassHash> months{
        {Month::January,   "January"},
        {Month::February,  "February"},
        {Month::March,     "March"},
        {Month::April,     "April"},
        {Month::May,       "May"},
        {Month::June,      "June"},
        {Month::July,      "July"},
        {Month::August,    "August"},
        {Month::September, "September"},
        {Month::October,   "October"},
        {Month::November,  "November"},
        {Month::December,  "December"},
    };
    
    std::cout << months.at(Month::January) << std::endl;
}

Выполнить код.

Ключевую роль здесь играет класс EnumClassHash. Он указывается в качестве шаблонного параметра при объявлении unordered_map. Этот класс возвращает хеш от элемента перечисления, при чем делает это максимально эффективным способом. В качестве хеша выступает значение самого элемента перечисления.

Таким образом можно создавать сколько угодно unordered_map c различными перечислениями, используя один единственный класс EnumClassHash. Иначе пришлось бы в пространстве имен std определять хеш-функцию для каждого перечисления.

Данный способ почерпнут в одном вопросе на StackOverflow.

Реклама

Аргументы командной строки в разных языках программирования

C и C++

int  main(int argc, char* argv[])
{
    argc;    // количество аргументов + 1
    argv[0]; // имя программы
    argv[1]; // первый аргумент
    argv[2]; // второй аргумент
    // ...
}

Bash

#!/bin/bash

# $# — количество аргументов
#
# $* — аргументы одной строкой
# $@ — список аргументов

echo $0    # имя скрипта
echo $1    # первый аргумент
echo $2    # второй аргумент
# ...
echo $9    # девятый аргумент
echo $(10) # десятый аргумент
echo $(11) # одиннадцатый аргумент
# ...

Cmd.exe

echo %0 REM имя срипта
echo %1 REM первый аргумент
echo %2 REM первый аргумент
REM ...
echo %9 REM девятый аргумент

Python

import sys

sys.argv      # список аргументов
len(sys.argv) # количество аргументов + 1
sys.argv[0]   # имя скрипта
sys.argv[1]   # первый аргумент
sys.argv[2]   # второй аргумент
# ...

Java

public class CommandLine {
    static public void main(String args[]) {
        args.length; // количество аргументов
        args[0];     // первый аргумент
        args[1];     // второй аргумент
        // ...
    }
}

C Sharp

class MainClass
{
    static int Main(string[] args)
    {
        args.Length; // количество аргументов
        args[0];     // первый аргумент
        args[1];     // второй аргумент
        // ...
    }
}

Обработка ошибок при выделение памяти с помощью new в C++

Есть два способа определить выделена ли память оператором new.

Способ 1. Обработка исключения

Если память не выделена, то бросается исключение std::bad_alloc.

Пример обработки исключения.

// bad_alloc example
#include <iostream>     // std::cout
#include <new>          // std::bad_alloc

int main () {
  try
  {
    int* myarray= new int[10000];
  }
  catch (std::bad_alloc& ba)
  {
    std::cerr << ba.what() << std::endl;
  }
  return 0;
}

Способ 2. Проверка указателя

При вызове new в качестве аргумента можно использовать константу std::nothrow. Тогда, исключение std::bad_alloc не испускается, а вместо него возвращается нулевой указатель.

Пример обработки нулевого указателя.

// nothrow example
#include <iostream>     // std::cout
#include <new>          // std::nothrow

int main () {
  std::cout << "Attempting to allocate 1 MiB... ";
  char* p = new (std::nothrow) char [1048576];

  if (!p) {
    std::cout << "Failed!\n";
  }
  else {
    std::cout << "Succeeded!\n";
    delete[] p;
  }

  return 0;
}

Ссылки

  1. Описание std::bad_alloc.
  2. Описание std::nothrow.

Удаление объектов в Qt

Эта статья учит избегать утечек памяти и ошибок сегментирования при программировании на C++ с библиотекой Qt.

qt segmetation fault fuuu

Читать далее Удаление объектов в Qt

Правила кодирования в Qt

640px-qt_logo_2016-svg

При использовании Qt я предпочитаю использовать стиль кодирования, который используется в библиотеке, чтобы добиться единообразия кода.

Директивы препроцессора

Символ решетки всегда в начале строки, а имя директивы с отступом.

Пример из файла Qt5.9.0/5.9/gcc_64/include/QtCore/qobjectdefs.h.

# if defined(QT_NO_KEYWORDS)
#  define QT_NO_EMIT
# else
#   ifndef QT_NO_SIGNALS_SLOTS_KEYWORDS
#     define slots Q_SLOTS
#     define signals Q_SIGNALS
#   endif
# endif

Ссылки и указатели

Операторы & и * в обозначении типа смещены вправо и примыкают к имени.

Пример из файла Qt5.9.0/5.9/gcc_64/include/QtCore/qbytearray.h.

class Q_CORE_EXPORT QByteArray
{
    <...>
    QByteArray &prepend(char c);
    QByteArray &prepend(int count, char c);
    QByteArray &prepend(const char *s);
    QByteArray &prepend(const char *s, int len);
    QByteArray &prepend(const QByteArray &a);
    QByteArray &append(char c);
    QByteArray &append(int count, char c);
    QByteArray &append(const char *s);
    QByteArray &append(const char *s, int len);
    QByteArray &append(const QByteArray &a);
    <...>
}

Приватные поля

В имени приватного поля используется префикс m_.

Пример из файла Qt5.9.0/5.9/gcc_64/include/QtCore/qstring.h.

class QLatin1String
{
public:
    <...>
private:
    int m_size;
    const char *m_data;
};

Ссылки

О других правилах кодирования можно узнать по следующим ссылкам.

C++. Частичная специализация шаблонного класса

Может пригодится, когда нужно для какого-то типа по своему реализовать метод или методы шаблонного класса не специализируя и не переписывая весь класс.

Пример.

Имеется следующий шаблонный класс в файле value.h.

template<typename T>
class Value {
public:
    Value(const T &val): m_value(val) {}
    T value() const { return m_value; }
private:
    T m_value;
};

Реализуем метод value для типа int в файле value.cpp.

template<>
int Value<int>::value() const
{
    return m_value + 1;
}

Поддержка ввода двоичных чисел в Си

Людям, занимающимся низкоуровневым программированием, часто хочется непосредственно ввести двоичную константу, но Си такой возможности не предоставляет. Только некоторые компиляторы поддерживают соответствующее расширение языка. Но если пользоваться таким расширением, то код становится непереносимым.

Чтобы иметь возможность ввода двоичных чисел и сохранить переносимость, вам пригодится этот заголовочный файл: bit.h.

Файл содержит код следующего вида:

#ifndef BIT_H
#define BIT_H

#define b0000_0000 0
#define b0000_0001 1
#define b0000_0010 2
// ...
#define b1111_1110 254
#define b1111_1111 255

#define b0000_0000_1 0
// ...
#define b1111_1111_1 65280

#define b0000_0000_2 0
// ...
#define b1111_1111_2 16711680

#define b0000_0000_3 0
// ...
#define b1111_1111_3 4278190080

#endif

Просто подключаем bit.h:

#include "bit.h"

и вводим двоичные числа, используя префикс b и разделяя тетрады подчеркиванием. Например:

#define BIT_MASK b0001_0101 // 21
const int a = b0011_1110;
...

При задании многобайтового значения такое число задаст только первый байт.
Чтобы задавать многобайтовые значения, требуется в конце двоичного числа ставить суффикс:

  • _1 — для смещения числа на 1 байт влево, то есть задания второго байта,
  • _2 — для смещения числа на 2 байта влево, то есть задания третьего байта,
  • _3 — для смещения числа на 3 байта влево, то есть задания четвертого байта,

при этом между числами требуется производить операцию или (|).

Например:

// 0xFF00FF00
uint32_t mask = b1111_1111_3 | b0000_0000_2 | b1111_1111_1 | b0000_0000;
...

Можно задавать один какой-то байт или несколько байт идущих не по порядку, например:

uint32_t mask = b1111_1111_3 | b1111_1111_1; // 0xFF00FF00
...