Инструменты пользователя

Инструменты сайта


cpp:notes

C++20 Notes

Числовые типы данных

    char c = '1';    // символ
    bool b = true;   // логическая переменная, принимает значения false и true
    int i = 42;      // целое число (занимает, как правило, 4 байта)
    short int i = 17;            // короткое целое (занимает 2 байта)
    long li = 12321321312;       // длинное целое (как правило, 8 байт)
    long long lli = 12321321312; // длинное целое (как правило, 16 байт)
    float f = 2.71828;           // дробное число с плавающей запятой (4 байта)
    double d = 3.141592;         // дробное число двойной точности (8 байт)
    long double ld = 1e15;       // длинное дробное (как правило, 16 байт)

По умолчанию типы знаковые, необходимо использовать "unsigned" для получения беззнаковых типов.

unsigned int ui = 4294967295;  // 2^32 - 1

Размер типа и минимальное-максимальное значение:

#include <limits>  // необходимо для numeric_limits

int main() {
    std::cout << "long long int: " << sizeof(long int) << "\n";

    // для типа int:
    std::cout << "minimum value: " << std::numeric_limits<int>::min() << "\n"
              << "maximum value: " << std::numeric_limits<int>::max() << "\n";
}

Размер long int на 64-разрядной машине составляет 8 байт, а на 32-разрядной машине 4 байта.

Типы с фиксированным размером, независящим от битности системы, из стандартной библиотеки:

#include <cstdint>

int8_t / uint8_t
int16_t / uint16_t
int32_t / uint32_t
int64_t / uint64_t

На некоторых аппаратных платформах операции с меньшей разрядностью, например uint16_t, могут занимать больше времени чем с аппаратным типом int. Чтобы выбрать тип необходимой разрядности, но не приводящий к снижению быстродействия, необходимо использовать определения std::int_fast#_t. Например, если на некоторой платформе:

  • std::int_fast8_t - вернет uint8_t
  • std::int_fast16_t - вернет uint32_t
  • std::int_fast32_t - вернет uint32_t

то это будет говорить о том, что процессор (микроконтроллер) одинаково быстро работает с типами uint8_t и uint32_t, а вот с числами uint16_t работает медленнее. (Скорость зависит от выборки из памяти и выполнений операций в АЛУ - Арифметико-Логическом Устройстве). Поэтому вместо вычислений с типом uint16_t оптимальнее будет использовать тип uint32_t, который покрывает собой весь диапазон uint16_t.

Есть похожие определения std::int_least8_t, std::int_least16_t, std::int_least32_t которые вернут тип, покрывающий данный диапазон и поддерживающийся в заданной платформе. Например если аппаратура не поддерживает тип uint8_t, а поддерживает только uint16_t и выше, то std::int_least8_t вернет тип uint16_t.

(Источник - radioprog.ru/post - 4.6 – Целочисленные типы фиксированной ширины и size_t).

При работе с беззнаковыми типами можно использовать их переполнение. Но переполнение знаковых типов считается UB, undefined behavior - не гарантируется правильная работа программы.

    // OK
    unsigned int x = 0;      // 0x0000_0000, на 64-битной платформе sizeof(x) == 4
    unsigned int y = x - 1;  // 0xFFFF_FFFF, 4294967295, то есть 2**32 - 1
    unsigned int z = y + 1;  // 0x0000_0000
    
    // UB, Произведение a * a не помещается в 4 байта, так как оно больше 2^32
    unsigned int a = 123456;  // на 64-битной платформе sizeof(a) == 4    
    std::cout << a * a << "\n";
Беззнаковые типы следует использовать, когда вы имеете дело с битовыми наборами. В остальных случаях предпочтительнее использовать знаковые типы. (Потому, что при вычитаниях беззнаковых типов получаются знаковые числа, что меняет тип операндов для последующих операций.)

Для получения дробного результата при делении необходимо одну из переменных привести к вещественному типу.

    int a = 7, b = 3;
    int q = a / b;  // 2
    int r = a % b;  // 1

    int c = 6, d = 4;
    double q = static_cast<double>(c) / d;  // 1.5

Автоматический вывод типа переменной:

    auto x = 42;  // int
    auto pi = 3.14159;  // double
    
    // НО для строк, выберется тип const char *, а не std::string!
    auto s = "hello"

size_t и ptrdiff_t

Отличная статья, объясняющая зоопарк с разрядностью типов int, long и т.д. - PVS-Studio Blog: Что такое size_t и ptrdiff_t

std::cin

    // содержит стандартные потоки cin, cout, cerr
    // для языка С они в stdio.h
    #include <iostream>


    // Считывание переменных через cin
    //При наборе данных на клавиатуре значения должны быть разделены:
    //  пробел, \n, \t
    int64_t a, b;
    char operation;    
    std::cin >> a >> operation >> b;
    
    // Считывание строки без спецсимволов: пробел, \n, \t
    std::string name;
    std::cin >> name;
    
    // Считывание строки включающей спецсимволы
    std::string line;
    std::getline(std::cin, line);
    for (char symbol : line) {
        std::cout << symbol << "\t" << static_cast<int>(symbol) << "\n"; // symbol and ascii code
    }    
    
    // Считывание пока не закончатся данные
    // При вводе данных не из файла, а с клавиатуры можно сымитировать конец ввода комбинацией клавиш:
    //   Ctrl+D в Linux и macOS или 
    //   Ctrl+Z в Windows.
    int sum = 0;
    int x;
    while (std::cin >> x) {
        sum += x;
    }
    std::cout << sum << "\n";
     
    // или
    
    std::string name;
    while (std::getline(std::cin, name)) {
        std::cout << "Hello, " << name << "!\n";
    }

Подробнее: prog-cpp.ru: Поточный ввод-вывод в C++

Преобразования строка / число

  • std::stod - строка в double
  • std::stof - строка в float
  • std::stold - строка в long double
  std::stod("123.0"); // 123
  std::stod("0.123"); // 0.123
  
  std::stod("123.4 with chars"); // 123.4
  std::stod("chars 1.2");        // исключение std::invalid_argument
  
  string str1 = "123.4 with chars";
  string str2 = "           123.4";
  size_t ptr1 = -1;
  size_t ptr2 = -1;
  auto m1 = std::stod(str1, &ptr1); // m1 = 123.4 ptr1 = 5
  auto m2 = std::stod(str2, &ptr2); // m2 = 123.4 ptr2 = 16

  std::cout << std::fixed << std::setprecision(1) << m1;

Всякое кодовство

  std::this_thread::sleep_for(std::chrono::milliseconds(500));

std::iota, std::fill

    QVector<uint8_t> table_lin(8);
    std::iota(table_lin.begin(), table_lin.end(), 0);
    // table_lin: 0, 1, 2, 3, 4, 5, 6, 7
    
    QVector<uint8_t> table(8);
    std::fill(table.begin(), table.end(), 3);
    // table: 3, 3, 3, 3, 3, 3, 3, 3

std::find_if

// CASE 1:
int indexOfWidgetRegistered(QWidget *widg)
{
    auto p_match = std::find_if(m_regWidgets.begin(), m_regWidgets.end(),
        [widg](Widg_RegItem *item){
          return (item->widg == widg);
        }
    );

    if (p_match != m_regWidgets.end())
        return p_match - m_regWidgets.begin();
    else
        return -1;
}

// CASE 2:
FormItem *MainWindow::getFormItemBySender(QAction *action)
{
    QList<FormItem*>::iterator match_item = std::find_if(m_formItems.begin(), m_formItems.end(),
            [action](FormItem *item){
              return (item->action == action);
            }
        );

    if (match_item != m_formItems.end())
        return *match_item;
    else
        return nullptr;
}

// CASE 3:
const QString c_addrNames[addrCount] = {
    "CtrlSw1",
    "CtrlSw2",
    "CtrlHV",
    "CtrlOffs1",
    "CtrlOffs2",
    "CtrlSw3",
    ...
}

QString rd_name = read from somewhere;

auto pmatch_name = std::find_if(std::begin(c_addrNames), std::end(c_addrNames),
                            [rd_name](const QString &name){
                             return name == rd_name;
                            });
if (pmatch_name != std::end(c_addrNames)) {
    i = pmatch_name - std::begin(c_addrNames);
    // index ready for next operations
} 
 struct UID_RegItem {
      Param_UIH *uih;
      QLabel    *lblName;
      QLabel    *lblUnits;
      bool       updNames;
      UID_RegItem(Param_UIH *_uih, QLabel *_lblName, QLabel *_lblUnits, bool _updNames)
          : uih{_uih}
          , lblName{_lblName}
          , lblUnits{_lblUnits}
          , updNames{_updNames}
      {}
};

int UI_ParamEventListener::indexOfRegWidget(const std::list<UID_RegItem> &items, const QWidget *widg)
{
    if (widg == nullptr)
        return -1;

    const auto p_match = std::find_if(items.begin(), items.end(),
        [widg](const UID_RegItem &item){
          return (item.uih->widget() == widg);
        }
    );

    if (p_match != items.end())
        return std::distance(items.begin(), p_match);
    else
        return -1;
}

std::fill

uint32_t m_values[addrCount];
uint32_t fill_value = 0;

std::fill(m_values, m_values + addrCount, fill_value);

std::minmax_element

    QVector<float> m_x;
    QVector<float> m_y;
    QRectF         m_boundingRect;
    size_t         m_pntCount;

    const auto [minY, maxY] = std::minmax_element(m_y.constBegin(), m_y.constBegin() + m_pntCount);
    m_boundingRect.setCoords(m_x.constFirst(), *minY, m_x.constLast(), *maxY);

Remove Items

The example removes all (key, value) pairs where the key and the value are the same.

QMutableMapIterator<QString, QString> i(map);
while (i.hasNext()) {
    i.next();
    if (i.key() == i.value())
        i.remove();
}

QMap Iteration

Исходный Map:

    typedef int32_t uid_t;
    
    struct Par_RegItem {
        // fields ...
    };

    QMap<uid_t, Par_RegItem*> m_regParams;

Неправильный вариант 1 - container-anti-pattern:

for (auto uid : m_regParams.keys()) {
    Par_RegItem *item = m_regParams[uid];
}

Warning: 
  allocating an unneeded temporary container [clazy-container-anti-pattern]
Recommended:
  for (auto i : hash.values()) {} // Iterate the hash directly instead: for (auto i : hash) {}

Неправильный вариант 2 - range-loop-detach:

for (auto hash : m_regParams) {
    Par_RegItem *item = hash; // hash это и есть //"Par_RegItem *"//
}

Warning: 
  c++11 range-loop might detach Qt container (QMap) [clazy-range-loop-detach]
Recommended: Fix it by marking the container const, or, since Qt 5.7, use qAsConst():
  for (auto i : qAsConst(list)) { ... }

Видимо так:

for (auto hash : qAsConst(m_regParams)) {
    Par_RegItem *item = hash;
}        

Итератор по Key and Value:

for (auto iter = m_regParams.constBegin(); iter != m_regParams.constEnd(); ++iter) {
        Par_RegItem *item = iter.value();
        uid_t uid = iter.key();
}    

ООП

  • При наследовании всегда делать деструктор виртуальным. Иначе при удалении объекта по указателю на базовый класс не вызовется деструктор наследника. Virtual Destructor
  • Не вызывать виртуальных методов в конструкторе и деструкторе. Calling virtual methods in constructor/destructor in C++

Определение констант

Под впечатлением от "Habr: Inline variables".

Константы и переменные объявленные (и определенные) в *.h файле без externt имеют внутреннее связывание. Т.е. эти переменные и константы получают свою копию в каждой единице трансляции. Они дублируются (занимают память) в каждый *.obj файл при компиляции *.cpp в которых подключен данный *.h файл.

При использовании externt к переменной, "ссылка" на эту внешнюю переменную выносится наружу *.obj файла и затем вместо "ссылки" линкер подставляет реальный адрес данной переменной, из той единицы трансляции в которой она определена.

Поэтому при создании констант до стандарта С++17, чтобы избежать дублирования, необходимо было определять константы в *.cpp файле, а объявлять их с externt в *.h файле.

// consts.h
namespace NT {
    namespace KeysFTDI {
        extern const char * const SerialNum;
        extern const char * const BuffLenTX;
        extern const char * const BuffLenRX;
        extern const char * const TimeoutTx_ms;
        extern const char * const TimeoutRx_ms;
        extern const char * const Latency_ms;
    }
}
// consts.cpp
namespace NT {
    namespace KeysFTDI {
        const char * const SerialNum    = "FTDI/SerialNum";
        const char * const BuffLenTX    = "FTDI/BuffLenTX";
        const char * const BuffLenRX    = "FTDI/BuffLenRX";
        const char * const TimeoutTx_ms = "FTDI/TimeoutTx_ms";
        const char * const TimeoutRx_ms = "FTDI/TimeoutRx_ms";
        const char * const Latency_ms   = "FTDI/Latency_ms";
    }
} // NT

constexpr - здесь не делает связывание внешним! Константы дублируются!

Начиная со стандарт С++17 можно использовать директиву inline для указания внешнего связывания для переменной определенной в *.h файле. Т.е. компилятор сам соберет все inline переменные / константы и расположит их в одной единице трансляции, из которой потом линкер подставит реальные адреса этих переменных/констант при сборке *.obj файлов.

Термин inline теперь разрешает множественное определение одной и той-же сущности для каждой единицы трансляции.

// consts.h
namespace NT {
    namespace KeysFTDI {
        inline const char * const SerialNum    = "FTDI/SerialNum";
        inline const char * const BuffLenTX    = "FTDI/BuffLenTX";
        inline const char * const BuffLenRX    = "FTDI/BuffLenRX";
        inline const char * const TimeoutTx_ms = "FTDI/TimeoutTx_ms";
        inline const char * const TimeoutRx_ms = "FTDI/TimeoutRx_ms";
        inline const char * const Latency_ms   = "FTDI/Latency_ms";
    }
}

Со статическими полями классов происходит все аналогично. На примере массива обработчиков некоего класса-парсера, до С++17 необходимо:

// Parser.h
<code>
class Parser
{
  private:
    // Тип функции - обработчика 
    typedef bool (*ParseHandler) (uint16_t *rx_data, uint16_t rx_count);
    
    // Функции обработчики протокола входных данных
    static bool parseMess(uint16_t *rx_data, uint16_t rx_count);
    static bool parseMessEx(uint16_t *rx_data, uint16_t rx_count);
    static bool parseFunc(uint16_t *rx_data, uint16_t rx_count);
    static bool parseReadBuff(uint16_t *rx_data, uint16_t rx_count);
    static bool parseOscData(uint16_t *rx_data, uint16_t rx_count);
    static bool parseOscReconf(uint16_t *rx_data, uint16_t rx_count);

    // Массив функций-обработчиков
    static ParseHandler parseHandlers[PC_ACK_COUNT];
    
    ...
}
// Parser.cpp
    Parser::ParseHandler Parser::parseHandlers[PC_ACK_COUNT] {
        &Parser::parseMess,
        &Parser::parseMessEx,
        &Parser::parseFunc,
        &Parser::parseReadBuff,
        &Parser::parseOscData,
        &Parser::parseOscReconf
};

Начиная с С++17 теперь можно так:

class Parser
{
  private:
    // Тип функции-обработчика 
    typedef bool (*ParseHandler) (uint16_t *rx_data, uint16_t rx_count);
    
    // Методы-обработчики протокола входных данных
    static bool parseMess(uint16_t *rx_data, uint16_t rx_count);
    static bool parseMessEx(uint16_t *rx_data, uint16_t rx_count);
    static bool parseFunc(uint16_t *rx_data, uint16_t rx_count);
    static bool parseReadBuff(uint16_t *rx_data, uint16_t rx_count);
    static bool parseOscData(uint16_t *rx_data, uint16_t rx_count);
    static bool parseOscReconf(uint16_t *rx_data, uint16_t rx_count);

    // Массив методов-обработчиков
    static inline ParseHandler parseHandlers[PC_ACK_COUNT] {
        &Parser::parseMess,
        &Parser::parseMessEx,
        &Parser::parseFunc,
        &Parser::parseReadBuff,
        &Parser::parseOscData,
        &Parser::parseOscReconf
    };
    
    ...
}

Замена #define

Заменяем С-шный #define на C++ шный inline constexpr:

// example.h
  #define MY_CONST_1  1
  
  inline constexpr int MY_CONST_2 = 2;       // Default choice
  inline const std::string MY_STR = "2";     // If not a literal type
  
  struct A {
    static constexpr int n = 5;              // Default choice; implicitly inline
    static inline const std::string s = "6"; // If not a literal type
  };
  
  
// example.cpp
  constexpr int MY_CONST_2 = 2;    // Default choice; implicitly static
  const std::string s4 = "4";      // If not a literal type; implicitly static
  
  void f() {
    static constexpr int n = 7;              // Default choice
    static const std::string s = "8";        // If not a literal type
  }
  
  

Массив методов-обработчиков

Вместо статических методов-обработчиков описаных выше, можно сделать так (спасибо Stackoverflow):

#include <iostream>
#include <array>
#include <functional>

struct Foo {
    std::array<std::function<void()>, 3> funArray; // Notice the change in signature to void()

    Foo() {     
        funArray[0] = [&](){ fun1(); }; // Here & is catching this by reference and this lambda will always call fun1 on the current object.
        funArray[1] = [&](){ fun2(); };
    }

    void fun1() {
        std::cout << "fun1\n";
    }

    void fun2() {
        std::cout << "fun2\n";
    }   

    std::function<void()> getFunction(int i) {
        return funArray[i];
    }
};  

int main() {
    Foo foo;
    foo.getFunction(0)(); // We get a function returned, so we need to call if by adding one more () at the end


    auto storedFunction = foo.getFunction(1); // We can also store it
    storedFunction(); // and call it later
}

QSplitter для нескольких Widget

QSplitter - это Widget который реализует "масштабирование" внутри себя своих child. Поэтому растягиваем QSplitter обычным Layout менеджером на всю форму в конструкторе. Задаем направление QSplitter - вертикальное или горизонтальное.

    m_layout = new QVBoxLayout;
    m_layout->setContentsMargins(0, 0, 0, 0);
    m_layout->setSpacing(0);
    setLayout(m_layout);

    m_splitter = new QSplitter(this);
    m_splitter->setChildrenCollapsible(false);
    m_splitter->setOrientation(Qt::Vertical);

    m_layout->addWidget(m_splitter);

Добавляем формы UiOscFrame в сплиттер по нажатию какой-нибудь кнопки Add обычным образом. Но с удалением формы из сплиттера есть нюансы. Надо вызвать Hide() для формы, чтобы место в сплиттере освободилось. Затем форму можно удалять. Для удобства создаваемые формы я храню в QList<UiOscFrame *> m_frames. Лог показывает, что m_splitter→count() показывает правильное количество виджетов в сплиттере при добавлении и удалении.

    // Add Widget:
    UiOscFrame *osc_frame = new UiOscFrame(this);
    m_frames.append(osc_frame);
    m_splitter->addWidget(osc_frame);
    qDebug() << "Add SplitterChilds: " << m_splitter->count();

    // Del Widget
    UiOscFrame *osc_frame = m_frames.takeAt(m_frames.count() - 1);
    osc_frame->hide();
    delete osc_frame;
    qDebug() << "Del SplitterChilds: " << m_splitter->count();

std::optional

std::optional<int32_t> smPosItemLo(AppID app)
{
    if (app > NT_SM_DLL::AppID_LaserExt)
        return {};

    int32_t value = ...

    return {value}; 
}

auto posItmLo = smPosItemLo(app);
if (posItmLo.has_value()) {
    ui->lbSelPos->setText(QString::number(*posItmLo));
}

std::pair

static std::pair<int32_t, bool> getMeanRange()
{
  int32_t retMeanRange = ...;
  bool retIsValid = ...;
  return {retMeanRange, retIsValid};
}

auto [meanRange, rangeValid] = getMeanRange(brd, sm);

std::countr_zero, Mask to BitIndex

int maskToBitInd(uint16_t mask)
{
    int indOf1 = std::countr_zero(mask);
    if (indOf1 == 16))   // 0 or 16 bits of zeros
        return -1;
    else
        return indOf1;
}

 countr_zero( 00000000 ) = 8
 countr_zero( 11111111 ) = 0
 countr_zero( 00011100 ) = 2
 countr_zero( 00011101 ) = 0

Времяпотребление

Источник - Habr: Числа, которые должен знать каждый программист

Обращение к кэшу L1 0.5 нс
Ошибка при предсказании условного перехода 5 нс
Обращение к кэшу L2 7 нс
Открытие/закрытие мьютекса 25 нс
Обращение к главной памяти 100 нс
Сжатие 1 Кб быстрым алгоритмом 3,000 нс
Пересылка 2Кб по сети со скоростью 1 Гб/с 20,000 нс
Чтение 1 Мб последовательно из главной памяти 250,000 нс
Передача сообщения туда/обратно в одном дата-центре 500,000 нс
Произвольный доступ к жёсткому диску 10,000,000 нс
Чтение 1 Мб последовательно с жёсткого диска 20,000,000 нс
Передача пакета из Калифорнии в Нидерланды и обратно 150,000,000 нс
cpp/notes.txt · Последнее изменение: 2024/04/17 13:26 — vasco