пятница, 9 января 2026 г.

Класс QString для C++Builder

ссылка для скачивания

 

Реализация Qt-подобного строкового класса для C++Builder

Если совсем кратко говорить, то суть в том, чтобы использовать Borland'овский стандартный класс UnicodeString через методы QString из Qt. Т.е. речь идет не о чем-то новом в плане разработки, а лишь об унификации стандартов: если уже есть опыт с QString и нет желания вникать в особенности UnicodeString, то можно использовать этот класс. Также использование классов с одинаковыми методами может быть удобно с точки зрения переносимости кода между разными средами разработки. Упрощенные реализации QChar и QStringList сделаны лишь в том контексте, что они требуются классу QString, не более того.

    QChar — упрощённый аналог Qt::QChar:

  •         Хранит один символ (wchar_t).
  •         Методы для проверки типа символа (isDigit, isLetter и т.д.).
  •         Преобразование регистра (toUpper, toLower).


    QString — основная строка:

  •         Внутреннее хранение через UnicodeString
  •         Поддерживает создание из разных источников: char*, wchar_t*, std::string, std::wstring.
  •         Реализует большинство методов Qt-строки:
  •             Поиск, разделение, замена, регистр.
  •             Подстроки (mid, left, right).
  •             Числовые преобразования (toInt, toDouble).
  •             Кодировки (UTF-8, Latin1, ISO-8859-1).
  •             Форматирование (arg, number). arg() реализована лишь частично (!)
  •             Операторы сравнения и конкатенации.


    QStringList — список строк:

  •         Наследуется от std::vector<QString>.
  •         Методы: join, contains, filter.
  •         Перегруженный operator<< для добавления строк. 

 

  1. Использует встроенные возможности C++Builder (UnicodeString, UTF8String, AnsiString).

  2. Поддерживает набор кодировок и преобразований между ними.

  3. Реализует специфичные методы Qt, такие как section, simplified, toHtmlEscaped.

  4. Учитывает особенности индексации UnicodeString (начинается с 1).

 

Модуль позволяет использовать Qt-подобный строковый API в проектах на C++Builder без подключения Qt, что полезно для миграции кода или поддержки кроссплатформенных исходников с Qt-стилем работы со строками.

 

Описание класса 

 

QString для C++Builder — строковый класс с Qt-подобным интерфейсом, построенный поверх UnicodeString. Он позволяет использовать привычные имена методов — mid(), split(), replace(), arg() и другие — в проектах C++Builder без подключения библиотеки Qt.

Идея модуля — унифицировать работу со строками. Если вы уже пользуетесь QString в Qt, часть строковой логики можно записывать в похожем стиле и в C++Builder. При этом хранение текста и взаимодействие с VCL опираются на штатные средства среды: UnicodeString, UTF8String и AnsiString.ия.

1. Состав модуля и подключение

  • qstring_nx_cb.h — классы QString и QStringList, преобразования строк и специализации std::hash для обоих классов.
  • qchar_nx_cb.h — QChar для одной кодовой единицы UTF-16 и вспомогательный QCharRef для изменения символа в строке.
  • qtnamespace_nx_cb.h — перечисления в namespace Qt и шаблон Qt::QFlags. Для строк используются, в частности, CaseSensitivity, SplitBehavior и SectionFlag.

Поместите все три файла в доступный для проекта каталог. Достаточно подключить qstring_nx_cb.h: два остальных заголовка он подключает сам. Реализация находится в заголовках, отдельного .cpp у этого модуля нет. Нужен компилятор с поддержкой используемых средств C++11, включая constexpr, noexcept и перемещение.

#include "qstring_nx_cb.h"

QString text(L"Привет, C++Builder!");
UnicodeString nativeText = text.toUnicodeString();

Эти классы объявлены в глобальном пространстве имён. Подключать их одновременно с настоящими одноимёнными классами Qt в одной единице трансляции нельзя: имена конфликтуют. Наличие GUI-перечислений в qtnamespace_nx_cb.h само по себе не добавляет виджеты или систему событий Qt.

2. Создание строк и взаимодействие с VCL

Строку можно создать из QChar, UnicodeString, const char*, const wchar_t*, std::string и std::wstring. Узкие строки const char* и std::string в этих конструкторах трактуются как UTF-8. Широкие строки в целевой Windows-среде передают текст UTF-16.

QString a("Hello");
QString b(L"Привет");
QString c = QString::fromUtf8("\xD0\xA2\xD0\xB5\xD0\xBA\xD1\x81\xD1\x82");
QString d(std::string("C++Builder"));
QString e(std::wstring(L"Широкая строка"));

UnicodeString native = L"Текст VCL";
QString f(native);
UnicodeString back = f;  // Неявное преобразование в UnicodeString

Для русскоязычных литералов в примерах используется L"…", чтобы не зависеть от кодировки узкого строкового литерала в настройках проекта. Если передавать кириллицу через обычный "…", важно, чтобы компилятор формировал именно UTF-8.

// Пример внутри обработчика формы VCL
QString userText(Edit1->Text);
QString message = QString(L"Введено: %1").arg(userText.trimmed());
Label1->Caption = message.toUnicodeString();

3. Длина, пустая строка и индексация

length() возвращает число кодовых единиц UTF-16. Индексы QString начинаются с нуля, хотя внутренний UnicodeString использует индексы с единицы. Пересчёт выполняется внутри класса.

QString s(L"Привет");
int n = s.length();       // 6
QChar first = s.at(0);    // Буква П
s[0] = QChar(L'п');      // Теперь "привет"
bool empty = s.isEmpty(); // false

s.clear();
bool isNull = s.isNull(); // true

В этой реализации isNull() совпадает с isEmpty(): отдельное состояние null-строки не сохраняется. at() при неверном индексе возвращает нулевой QChar; константный operator[] ведёт себя так же. Неконстантный operator[] проверяет границы и при неверном индексе выбрасывает std::out_of_range.

Один отображаемый знак не всегда занимает одну позицию. Например, многие эмодзи представлены суррогатной парой и дают length() == 2. at(), mid(), left(), right() и другие операции с позициями работают с кодовыми единицами UTF-16; при неосторожном разрезании можно разделить пару.

4. Поиск текста

indexOf() ищет первое вхождение, lastIndexOf() — последнее. Результат — индекс с нуля; если совпадения нет, возвращается -1. contains(), startsWith() и endsWith() отвечают на вопрос о наличии подстроки, префикса или суффикса.

QString path("config/device.ini");
int slash = path.indexOf("/");       // 6
int dot = path.lastIndexOf(".");     // 13
bool found = path.contains("device"); // true
bool ini = path.endsWith(".ini");     // true
bool cfg = path.startsWith("config"); // true
int absent = path.indexOf(".xml");    // -1

int overlaps = QString("aaaa").count("aa"); // 3: перекрытия учитываются

У методов поиска QString в этом заголовке нет параметра Qt::CaseSensitivity: поиск чувствителен к регистру. Для простого поиска без учёта регистра можно преобразовать обе строки одним и тем же способом и использовать contains(). Если нужны позиции в исходной строке, учитывайте, что преобразование регистра может менять длину текста.

QString name(L"ДАТЧИК температуры");
bool found = name.toCaseFolded().contains(
   QString(L"датчик").toCaseFolded()); // true

5. Получение подстрок и секций

left(n) и right(n) возвращают начало или конец строки, mid(position, n) — фрагмент заданной длины. Если n у mid() опущен, берётся остаток строки. Исходная строка при этом не меняется.

QString name("report_2026.csv");
QString prefix = name.left(6);    // "report"
QString ext = name.right(3);      // "csv"
QString year = name.mid(7, 4);    // "2026"
QString tail = name.mid(7);       // "2026.csv"

section() извлекает одну или несколько частей, разделённых заданной строкой. Номера частей начинаются с нуля, конечная граница включается. Отрицательные номера отсчитываются от конца.

QString key("plant/line/device/tag");
QString device = key.section("/", 2, 2); // "device"
QString group = key.section("/", 1, 2);  // "line/device"
QString tag = key.section("/", -1, -1);  // "tag"

Для section() предусмотрены флаги SectionSkipEmpty, SectionIncludeLeadingSep и SectionIncludeTrailingSep. В отличие от mid(), здесь индекс задаёт номер части, а не позицию в символах.

6. Добавление, вставка, замена и удаление

append(), prepend(), insert(), replace(), remove() и operator+= изменяют текущую строку. Они возвращают QString&, поэтому вызовы можно объединять. operator+ создаёт новый результат.

QString s("sensor");
s.prepend("[").append("]"); // "[sensor]"
s.insert(1, "new_");       // "[new_sensor]"
s.replace("new_", "");     // "[sensor]"
s.remove(0, 1);            // "sensor]"
s.chop(1);                 // "sensor"

QString title = QString("Device: ") + s;
s += "_01";                // "sensor_01"

У remove(position, n) второй аргумент означает количество удаляемых кодовых единиц. remove(text) удаляет все вхождения текста. chop(n) убирает n единиц с конца; truncate(pos) оставляет первые pos единиц. chop() и truncate() возвращают void.

QString s("Status: OK; status: OK");
s.replace("status", "State", Qt::CaseInsensitive);
// "State: OK; State: OK"

s.truncate(9);
// "State: OK"

7. Пробелы и регистр

trimmed() удаляет пробельные символы по краям. simplified() дополнительно заменяет внутренние последовательности пробельных символов одним обычным пробелом. Методы возвращают новую строку.

QString raw(L"  Датчик\t\n  давления  ");
QString a = raw.trimmed();
// "Датчик\t\n  давления"
QString b = raw.simplified();
// "Датчик давления"

QString upper = QString(L"Привет").toUpper(); // "ПРИВЕТ"
QString lower = QString(L"Привет").toLower(); // "привет"

toUpper() и toLower() используют System::Character::TCharacter. Метод toCaseFolded() содержит таблицу полного преобразования регистра Unicode и может заменять один символ несколькими. Его удобно применять при сравнении текста без учёта регистра; это не то же самое, что простое приведение к нижнему регистру.

bool same = QString(L"Straße").toCaseFolded()
      == QString("STRASSE").toCaseFolded(); // true

8. Разделение строки и QStringList

split() возвращает std::vector<QString>. По умолчанию пустые части сохраняются; Qt::SkipEmptyParts исключает их. Чтобы пользоваться join(), filter() и другими методами QStringList, явно создайте список из результата split().

QString source("red;;green;blue;");
auto all = source.split(";");
// 5 элементов: "red", "", "green", "blue", ""

QStringList colors(source.split(";", Qt::SkipEmptyParts));
QString joined = colors.join(", ");
// "red, green, blue"

QStringList содержит приватный std::vector<QString>, а не наследуется от него. Он предоставляет итераторы, size(), empty(), reserve(), push_back(), insert(), erase() и другие операции в стиле vector. Добавлять строки можно и привычным operator<<.

QStringList files;
files << "report.csv" << "notes.txt" << "backup.CSV";

QStringList csv = files.filter(".csv", Qt::CaseInsensitive);
QString list = csv.join("; "); // "report.csv; backup.CSV"
bool hasNotes = files.contains("notes.txt"); // true

for(const QString& file : csv)
{
   UnicodeString nativeName = file.toUnicodeString();
   // Здесь можно передать nativeName в VCL
}

contains() у списка проверяет наличие целой строки, а filter() выбирает строки, содержащие указанную подстроку. Оба метода принимают CaseSensitivity. Сортировка без учёта регистра, фильтрация и поиск списка используют toLower(), а не полный toCaseFolded().

QStringList names = {"beta", "alpha", "beta"};
names.removeDuplicates(); // Сохраняет первые вхождения
names.sort();
QString result = names.join(" | "); // "alpha | beta"

names.replaceInStrings("alpha", "ALPHA");
QString missing = names.value(100); // Пустая строка
std::vector<QString> copy = names.toStdVector();

value(i) возвращает пустую строку при выходе за границы. first() и last() тоже возвращают пустую строку для пустого списка. removeDuplicates() возвращает void и сравнивает строки с учётом регистра. Для обычного operator[] списка действуют правила vector: индекс нужно проверять.

9. Чтение чисел из текста

toInt(), toDouble() и toFloat() поддерживают необязательный указатель bool* ok. При неудаче возвращается ноль и ok становится false. Проверять ok необходимо, если нужно отличить ошибку от корректного числа 0. Пробелы по краям удаляются, остаток строки должен целиком соответствовать числу.

bool ok = false;
int count = QString(" 42 ").toInt(&ok); // 42, ok == true
double value = QString("12.5").toDouble(&ok); // 12.5, true
int bad = QString("42px").toInt(&ok); // 0, false
int zero = QString("0").toInt(&ok);   // 0, true

В данной реализации toInt() не имеет параметра основания системы счисления. toDouble() и toFloat() используют точку как десятичный разделитель независимо от региональных настроек; строка "12,5" не является допустимой записью. Поддерживаются специальные значения inf, +inf, -inf и nan; для них ok устанавливается в true.

10. Форматирование чисел и arg()

QString::number(int) формирует десятичное целое. Перегрузка number(double, format, precision) поддерживает фиксированную форму f/F, экспоненциальную e/E и общую g/G. Для f точность задаёт число знаков после точки, для e — после точки в мантиссе, для g — число значащих цифр. По умолчанию здесь используется f с точностью 6.

QString a = QString::number(42);             // "42"
QString b = QString::number(12.5, 'f', 2);   // "12.50"
QString c = QString::number(1250.0, 'e', 2); // "1.25e+03"
QString d = QString::number(12.5, 'g', 3);   // "12.5"

arg() принимает QString, ширину поля и QChar заполнения. Каждый вызов заменяет все маркеры с наименьшим найденным номером в диапазоне от %1 до %99. Цепочка вызовов последовательно подставляет несколько значений. Для числа с нужным форматом сначала вызывайте number().

QString message = QString(L"Канал %1: %2 В")
.arg(QString::number(3))
.arg(QString::number(12.5, 'f', 2));
// "Канал 3: 12.50 В"

QString padded = QString("ID=%1")
.arg(QString::number(7), 4, QChar(L'0'));
// "ID=0007"

QString repeated = QString("%1 + %1 = %2")
.arg("A").arg("B");
// "A + A = B"

Положительная ширина arg() выравнивает текст вправо, отрицательная — влево. Строка длиннее поля не обрезается. Числовых перегрузок arg() с основанием, форматом и точностью здесь нет. Маркеры вида %L1 распознаются, но локализованное форматирование чисел не выполняется.

Конструктор QString(double) использует FloatToStr(), поэтому зависит от региональных настроек. QString::number(double, …) формирует текст с точкой. Для файлов, протоколов и предсказуемых примеров предпочтительнее явный вызов number().

Также есть статические sprintf(), asprintf() и vsprintf(). Они используют printf-форматирование, после чего трактуют полученные байты как UTF-8. Типы аргументов должны соответствовать спецификаторам.

QString s = QString::asprintf("Channel %d", 3);
// "Channel 3"

11. Кодировки и обмен со стандартными строками

Для байтового текста важно явно знать кодировку. UTF-8, локальная кодовая страница Windows и Latin-1 — разные способы представления текста. В частности, Latin-1 не является Windows-1251.

Метод / источникЗначение
QString(const char*), QString(std::string)Входные байты UTF-8
fromUtf8(), fromStdString()Явное чтение UTF-8
toUtf8(), toStdString()Возвращают std::string с UTF-8
fromLocal8Bit(), toLocal8Bit()Обмен через AnsiString и локальную ANSI-кодовую страницу
fromLatin1(), fromIso8859_1()Чтение ISO-8859-1; второй метод — псевдоним первого
fromStdWString(), toStdWString()std::wstring; в целевой Windows-среде UTF-16
fromStdU16String(), toStdU16String()std::u16string, кодовые единицы UTF-16
fromStdU32String(), toStdU32String()std::u32string, преобразование кодовых точек
QString text(L"Привет");
std::string bytes = text.toUtf8();
QString restored = QString::fromUtf8(
   bytes.data(), static_cast<int>(bytes.size()));
bool same = text == restored; // true

const char latin1[] = {char(0xE9)};
QString accent = QString::fromLatin1(latin1, 1); // "é"

Перегрузки с параметром size позволяют передавать буфер заданной длины, в том числе с нулевыми байтами внутри. Без size байтовая строка читается до первого нулевого байта. Параметр size имеет тип int: если размер приходит из большого внешнего буфера как size_t, перед приведением проверьте его диапазон.

toUtf8() возвращает именно std::string, а не QByteArray. Поэтому указатель на байты получают через c_str() или data(). Храните сам std::string столько времени, сколько используется указатель.

std::string utf8 = text.toUtf8();
const char* bytes = utf8.c_str();
// bytes действует, пока utf8 существует и его буфер не изменён

12. QChar и изменение отдельной позиции

QChar хранит один wchar_t, то есть в целевой Windows-среде одну кодовую единицу UTF-16. Доступны isNull(), isValid(), isDigit(), isLetter(), isSpace(), isUpper(), isLower(), isLetterOrNumber(), digitValue(), toUpper(), toLower(), unicode(), unicode16() и toLatin1().

QChar letter(L'я');
bool isLetter = letter.isLetter(); // true
QChar upper = letter.toUpper();   // Я

QChar digit(L'7');
int value = digit.digitValue();    // 7
int absent = letter.digitValue();  // -1

QString s("abc");
s[1] = QChar(L'X');              // "aXc"
QChar c = s.at(1);

Есть константы QChar::Null, Space, LineFeed, CarriageReturn и Tabulation. Конструкторы из char, wchar_t и char16_t являются explicit. QChar(char) рассматривает один байт как беззнаковое значение, а не декодирует последовательность UTF-8. Для русской буквы используйте, например, QChar(L'Я').

isValid() здесь означает лишь «значение не равно нулю»: он не проверяет корректность Unicode и не исключает суррогаты. toLatin1() возвращает 0, если значение не помещается в диапазон 0…255. QCharRef служит посредником при записи через неконстантный QString::operator[]; его не следует сохранять после уничтожения исходной строки.

13. Повторение, выравнивание и HTML

QString line = QString("-=").repeated(3); // "-=-=-="
QString left = QString("A").leftJustified(4, QChar(L'.'));
// "A..."
QString right = QString("A").rightJustified(4, QChar(L'0'));
// "000A"

QString mask;
mask.fill(QChar(L'*'), 5); // "*****"

QString html = QString("<b>A & B</b>").toHtmlEscaped();
// "&lt;b&gt;A &amp; B&lt;/b&gt;"

toHtmlEscaped() экранирует &, <, >, двойную и одинарную кавычки. Метод подходит для вывода текста в HTML, но не заменяет обработку URL, JavaScript или других специальных контекстов. repeated() и методы выравнивания возвращают новую строку; fill() изменяет текущую.

14. Доступ к буферу и capacity()

constData(), константный data() и utf16() возвращают const wchar_t* на внутренний UTF-16-буфер. Неконстантный data() возвращает wchar_t* и перед этим вызывает UnicodeString::Unique(), отделяя разделяемые данные.

QString s("abc");
wchar_t* buffer = s.data();
buffer[1] = L'X'; // "aXc"; индекс буфера также с нуля

Записывайте только в существующие позиции 0…length()-1. Через указатель нельзя увеличивать длину строки или перезаписывать завершающий ноль. После изменения строки прежний указатель может стать недействительным; указатель на буфер временного объекта сохранять нельзя. Для пустой строки data() предоставляет место под нулевой терминатор, а не свободный буфер для добавления текста.

capacity() у QString возвращает текущую длину, а не реальную ёмкость выделенной памяти. Метода reserve() у QString в этом модуле нет. У QStringList методы reserve() и capacity() относятся к внутреннему vector и имеют обычный смысл для контейнера.

15. Сравнение и стандартные контейнеры

QString поддерживает ==, !=, <, <=, > и >=. Сравнение строк выполняется через UnicodeString и чувствительно к регистру. В заголовке также определены std::hash<QString> и std::hash<QStringList>, поэтому их можно использовать как ключи стандартных хеш-контейнеров.

#include <unordered_map>

std::unordered_map<QString, int> counters;
counters[QString("sensor_A")] = 10;
counters[QString("sensor_B")] = 20;
int value = counters[QString("sensor_A")]; // 10

16. Практический пример: разбор строки параметров

Небольшой пример объединяет разделение, удаление лишних пробелов, проверку числа и формирование результата. Формат входных данных здесь простой: имя;значение;единица измерения. Это не CSV-парсер с поддержкой кавычек и экранирования.

QString input(L"  Давление ; 12.5 ; бар  ");
QStringList fields(input.split(";", Qt::KeepEmptyParts));
QString result;

if(fields.size() == 3)
{
   bool ok = false;
   double value = fields[1].trimmed().toDouble(&ok);
   if(ok)
   {
      result = QString(L"%1: %2 %3")
      .arg(fields[0].trimmed())
      .arg(QString::number(value, 'f', 2))
      .arg(fields[2].trimmed());
   }
   else
   {
      result = QString(L"Ошибка: неверное число");
   }
}
else
{
   result = QString(L"Ошибка: ожидаются три поля");
}
// Для этой строки: "Давление: 12.50 бар"
// В обработчике формы можно вывести:
// Label1->Caption = result.toUnicodeString();

17. Что учитывать при переносе кода из Qt

  • Набор API ограничен. В статье перечислены методы именно этого модуля; поддержка всех возможностей Qt QString, регулярных выражений и локалей не заявляется.
  • split() → vector. Для QStringList требуется явное создание из результата.
  • Байтовые результаты → std::string. У них нет методов QByteArray.
  • Поиск QString чувствителен к регистру. Параметр CaseSensitivity есть у replace() и ряда операций QStringList, но не у indexOf() или contains() строки.
  • arg() принимает строковое значение. Поддержка числовых форматов выполняется отдельным вызовом number().
  • isNull() == isEmpty(). Различие пустой и null-строки не моделируется.
  • capacity() строки — длина. Использовать его для оценки запаса памяти нельзя.
  • Зависимость от C++Builder сохраняется. Сходный интерфейс помогает переносить строковую логику, но сама реализация использует System.hpp и RTL C++Builder.

Модуль полезен, когда хочется сохранить Qt-стиль обработки строк в проекте C++Builder и удобно передавать результат в VCL через UnicodeString. При переносе конкретного участка кода следует сверять используемые перегрузки и ожидаемое поведение с этими заголовками.

 

понедельник, 7 июля 2025 г.

Динамический массив C++ без реаллокаций

ссылка для скачивания

В архиве три файла для C++ (требуется поддержка компилятором C++11): 


1. Qt

2. Visual C++

3. C++Builder

Версия для Visual C++ является базовой. В версии для Qt добавлена поддержка строковых литералов для QString и работа с QList. В версии для C++Builder добавлена поддержка строковых литералов для UnicodeString.  

 

Реализация динамического массива на C++, обеспечивающего стабильность указателей.

Под стабильностью указателей понимается неизменность адресов в памяти, где располагаются объекты, являющиеся элементами массива. Т.е. указатель-переменная, созданный отдельно от массива и указывающий на любой его элемент, сохранит актуальность независимо от того, какие действия производятся с этим массивом.

1. Назначение и устройство

nx_array<T, Allocator> объединяет хранение элементов в узлах std::list<T, Allocator> и доступ по индексу через std::vector<T*>. Объекты T находятся в списке, а вектор содержит их адреса в порядке следования элементов.

Такая организация обеспечивает доступ по индексу за O(1) и сохраняет адреса существующих элементов при добавлении новых. Перевыделяться может внутренний вектор указателей; сами объекты T при этом не перемещаются.

Элементы не образуют непрерывного массива T. Контейнер расходует дополнительную память на узлы списка и вектор указателей; обход может уступать std::vector по эффективности работы с кэшем процессора.

По исходному описанию предусмотрены варианты для Visual C++, Qt и C++Builder. Этот документ проверен только для Qt-версии. В ней есть перегрузки для строковых литералов с QString и поддержка QList. Заголовок использует возможности C++11; требования конкретной версии Qt к компилятору и стандарту языка действуют дополнительно.

2. Границы гарантии стабильности

Указатели и ссылки на сохраняемые элементы остаются действительными при вставке, удалении других элементов, reserve(), shrink_to_fit(), а также при перестановках узлов методами moveItem(), swapItems(), reverse() и sort(). Индекс элемента при этом может измениться.

Пример:

nx_array<QString> a;
a += "B";
QString* p = &a[0];
a.addItem("A", 0);
// Теперь a содержит {"A", "B"}; p == &a[1], *p == "B".

Удаление самого элемента, clear(), уничтожение контейнера и замена его содержимого могут прекратить время жизни объекта и сделать указатель недействительным. Присваивание нового значения существующему элементу сохраняет его адрес, но меняет содержимое.

При spliceArray() узлы переходят в другой контейнер без изменения адресов. После ejectStdList() элементы принадлежат возвращённому списку; указатели действуют до удаления соответствующих элементов из этого списка.

При поэлементном переносе в std::vector, std::deque, QList или std::array создаются другие объекты. Старые адреса элементов nx_array не сохраняются после очистки источника.

Для пользовательских аллокаторов нельзя обещать сохранение адресов при любом перемещающем присваивании: при несовместимых аллокаторах реализация может переносить значения в новые узлы. Перенос узлов через splice требует равных аллокаторов.

Стабильность адреса не означает неизменность значения. Например, стандартный алгоритм, переставляющий значения через итераторы, может изменить содержимое объектов по прежним адресам. Для сортировки с сохранением связи адреса с объектом используйте метод sort().

3. Указатели и итераторы

Итераторы nx_array хранят адрес контейнера и индекс, а не идентификатор узла. После вставки, удаления, перестановки, swap() или переноса содержимого прежний итератор может обозначать другой элемент либо оказаться вне диапазона. Гарантия стабильности указателей на элементы не распространяется на привязку итераторов к этим элементам.

checkIterator(it) проверяет принадлежность контейнеру и условие index <= size(). Итератор end() проходит эту проверку, хотя разыменовывать его нельзя. Метод не выявляет все устаревшие итераторы и не проверяет, сохранился ли прежний элемент на указанной позиции.

isPointerValid(ptr) проверяет, совпадает ли переданный адрес с адресом одного из текущих элементов. Это поиск за O(n), а не универсальная проверка безопасности указателя. Если память удалённого элемента была повторно использована, совпадение адреса не доказывает, что это прежний объект.

difference_type getItemIndex(const T* ptr) возвращает текущий индекс элемента либо -1; сложность O(n).
T* getItemPtr(size_type index) возвращает адрес элемента, проверяя индекс.

При T = Widget* элементом контейнера является сам указатель: адрес элемента имеет тип Widget**, а адрес объекта Widget — тип Widget*. Эти два уровня адресов следует различать.

4. Создание контейнера

nx_array<int> a;                    // Пустой контейнер.
nx_array<int> b(5);                 // Пять элементов со значением 0.
nx_array<int> c(3, 10);             // Три элемента; capacity() >= 10.
nx_array<int> d{1, 2, 3};           // Три заданных значения.
nx_array<int> e({1, 2, 3}, 10);     // Три значения; capacity() >= 10.
nx_array<QString> s{"a", "b", "c"};

Конструктор std::initializer_list объявлен explicit. Используйте прямую инициализацию фигурными скобками, как выше, вместо записи nx_array<int> a = {1, 2, 3}.

Резерв относится только к вектору указателей. Он не создаёт запасных объектов T и не резервирует узлы списка. Фактическая ёмкость может превышать запрошенную.

Копирование и перемещение nx_array:

nx_array<int> original{1, 2, 3};
nx_array<int> copy(original);                 // Копирование элементов.
nx_array<int> moved(std::move(original));     // Перенос хранилища; original пуст.
// Альтернативный вызов для неконстантного nx_array:
// nx_array<int> moved(original, 0, true);

Создание из других контейнеров:

std::vector<QString> v{"A", "B"};
nx_array<QString> copied(v, 10);              // Копирование.
nx_array<QString> moved(std::move(v));        // Поэлементное перемещение.

Общий rvalue-конструктор для vector/deque/QList не вызывает clear() у источника: размер источника сохраняется, значения его элементов могут измениться после перемещения.

Для std::list предусмотрен перенос узлов:

std::list<int> lst{1, 2, 3};
nx_array<int> a(std::move(lst), 10);          // Узлы перенесены, lst пуст.

Вызов nx_array<int> a(lst, 10, true) выбрасывает std::invalid_argument. Для передачи узлов используйте std::move(lst).

Параметр to_move в конструкторах из const std::vector&, const std::deque&, const QList& и const std::array& не гарантирует реальное перемещение: элементы доступны как const, поэтому для обычных копируемых типов выполняется копирование. Для перемещения значений используйте неконстантные итераторы с флагом true либо rvalue-конструктор.

Создание из диапазона:

std::vector<QString> v{"A", "B"};
nx_array<QString> a(v.begin(), v.end());
nx_array<QString> b(v.begin(), v.end(), 10, true);
// Второй вызов перемещает значения, но не удаляет элементы v.

Создание из обычного массива:

int raw[]{1, 2, 3};
nx_array<int> a(3, raw);
nx_array<int> b(3, raw, 10, true);

Размер массива-источника не изменяется. При ненулевом количестве элементов нулевой адрес запрещён. Попытка перемещения из const-массива с ненулевым количеством элементов вызывает std::invalid_argument.

5. Доступ и ёмкость

size(), count() без аргументов — количество элементов.
empty() — проверка на отсутствие элементов.
capacity() — ёмкость внутреннего вектора указателей.
reserve(n) — обеспечивает capacity() >= n, не уменьшая ёмкость.
shrink_to_fit() — пытается уменьшить избыточную ёмкость, пересоздавая вектор указателей.
max_size() — верхняя граница количества элементов с учётом обоих внутренних контейнеров.

operator[](index) и at(index) возвращают ссылку и в этой реализации оба проверяют индекс, выбрасывая std::out_of_range при index >= size(). Имеются константные перегрузки.
front() и back() возвращают ссылки на первый и последний элементы; контейнер должен быть непустым.

const T* const* data() const возвращает адрес внутреннего массива указателей, а не непрерывный массив объектов T. Неконстантная перегрузка data() удалена, поэтому для чтения нужно обращаться через константную ссылку. Перевыделение внутреннего вектора делает ранее полученный адрес его буфера недействительным.

6. Добавление, изменение и удаление

START_POS = 0 — начало; END_POS = -1 — конец.

addItem(value, position = END_POS, useExc = false) добавляет значение копированием либо перемещением в зависимости от перегрузки. Допустимы позиции от 0 до size(), а также END_POS. При некорректной позиции и useExc == false элемент добавляется в конец; при useExc == true выбрасывается std::out_of_range. Флаг не подавляет остальные исключения, например ошибки выделения памяти или конструктора T.

emplaceItem(position, args...) создаёт T непосредственно в новом узле и возвращает T&. Некорректная позиция вызывает исключение.
push_back(), push_front() добавляют элементы в конец и начало.
emplace_back(), emplace_front() конструируют элементы на месте; в этой реализации возвращают void.
insert() и emplace() работают с позицией-итератором и возвращают итератор вставленного элемента.

moveItem(from, to) переносит узел на другую позицию, сохраняя его адрес.
swapItems(i, j) меняет узлы местами, сохраняя адреса обоих объектов.
reverse() меняет порядок узлов на обратный.
replace(index, value) присваивает элементу новое значение без удаления объекта, на который мог указывать прежний сырой указатель.

resize(n) уменьшает контейнер либо добавляет в конец элементы, конструируемые без аргументов. Для int добавляются нули.
resize(n, value) при увеличении добавляет копии value.
setValue(value, start, finish = END_POS) присваивает value элементам диапазона с включённой правой границей. Для пустого контейнера ничего не делает.

removeItem(index) удаляет элемент по индексу.
removeRange(start, finish = END_POS) удаляет включительный диапазон [start, finish]. END_POS или finish >= size() означает последний элемент. start должен обозначать существующий элемент; start > finish вызывает исключение.
erase(first, last) удаляет полуинтервал [first, last), как в стандартных контейнерах.
erase(pos) возвращает итератор следующего элемента либо end().
erase(value) удаляет первое совпадение; erase_all(value) — все совпадения, возвращая их количество.
erase_if(pred) удаляет все элементы, удовлетворяющие предикату.
clear() удаляет все элементы, сохраняя ёмкость вектора указателей.

Обычные методы удаления разрушают объекты T. Если T — сырой указатель, они удаляют только значение указателя из контейнера, не вызывая delete для адресуемого объекта.

T extract(index) и T extract(pos) перемещают значение в возвращаемый объект и удаляют исходный узел. Требуется std::is_nothrow_move_constructible<T>::value == true; несоответствие выявляется при компиляции. Адрес извлечённого узла не сохраняется. Это собственный метод nx_array, не std::list::extract: у std::list такого метода нет.

T pop_back() извлекает и возвращает последний элемент. Этим он отличается от std::vector::pop_back(), возвращающего void.

7. Поиск, сортировка и обработка

contains(value) проверяет наличие значения.
find(value) возвращает индекс либо -1.
find_itr(value) возвращает итератор либо end().
Эти методы выполняют линейный поиск. Необязательный bool* ok позволяет отличить обычное отсутствие значения от перехваченного исключения сравнения.
count(value) подсчитывает совпадения. Есть перегрузка с функцией сравнения.

sort() выполняет стабильную сортировку по возрастанию с std::less<T>.
sort(comp) использует заданный компаратор, который должен задавать строгий слабый порядок. Узлы переставляются без перемещения объектов T. При успехе возвращается true, при перехваченном исключении — false. Отсутствие подходящего оператора < или некорректный компаратор может вызвать ошибку компиляции, а не возврат false.

apply(func) вызывает функцию для каждого элемента; имеется константная перегрузка.

8. Копирование и присоединение контейнеров

addArray(source, position = END_POS) при передаче обычного lvalue-контейнера копирует элементы. Есть перегрузки для nx_array, std::array, std::vector, std::list, std::deque, QList и обычных массивов с явно указанным размером.

addArray(std::move(source), position) выбирает rvalue-перегрузку. Для nx_array того же типа и list_type переносятся узлы, источник опустошается, адреса сохраняются. Для std::vector, std::deque и QList значения перемещаются в новые узлы, затем источник очищается. Для std::array размер остаётся прежним. При другом типе nx_array может применяться поэлементный перенос, не сохраняющий исходные адреса.

Нельзя описывать все rvalue-перегрузки как const Container&&: перемещению обычно требуется неконстантный источник. Перегрузки для const nx_array&& копируют либо отклоняют недопустимую операцию.

spliceArray(source, position) переносит узлы из list_type или nx_array того же типа, сохраняя адреса. Требуются равные аллокаторы; при передаче между массивами сырых указателей проверяется совместимость режимов владения. Самоперенос nx_array запрещён.

mergeArray(source, position) явно переносит содержимое неконстантного источника. Для vector/deque/QList выполняется поэлементный перенос с очисткой источника. В текущей версии есть также перегрузки для nx_array и list_type, вызывающие spliceArray(), и перегрузка для обычного массива, размер которого остаётся прежним.

addArray(ptr, count, position, useExc) копирует обычный массив. При ptr == nullptr и count != 0 исключение возникает независимо от useExc. При count == 0 метод ничего не делает. Флаг useExc управляет проверкой позиции.

9. Преобразования

a.toStdList() возвращает копию элементов в std::list.
a.ejectStdList() и std::move(a).toStdList() передают узлы в результирующий список, опустошая a и сохраняя адреса элементов. Возвращается отдельный объект списка, получивший узлы внутреннего хранилища.

a.toStdVector(), a.toStdDeque(), a.toQList() копируют значения.
std::move(a).toStdVector(), std::move(a).toStdDeque(), std::move(a).toQList() переносят значения в новые элементы результата и при успешном выполнении очищают a. Перенос в QList реализован через append(std::move(item)). Для toStdVector() и toQList() есть необязательный параметр желаемой ёмкости результата.

a.toStdArray<N>() требует size() == N, иначе выбрасывает std::length_error. Для lvalue значения копируются присваиванием, для rvalue — перемещаются присваиванием, после чего источник очищается. Требования к T определяются этими операциями и созданием результирующего std::array.

Перемещение может фактически вызвать копирование, если это допускает тип T и для выражения отсутствует подходящий перемещающий конструктор. Если поэлементное перемещение выбрасывает исключение, часть значений источника уже может быть изменена; общую гарантию полного отката для всех методов предполагать нельзя.

10. Операторы

Оператор = заменяет содержимое; копирование или перемещение определяется перегрузкой и категорией аргумента.
Оператор += добавляет значение либо элементы контейнера; rvalue-перегрузки для контейнеров могут переносить содержимое.
Оператор + возвращает результат объединения; поведение временных операндов зависит от выбранной перегрузки.
Оператор <<= явно заменяет содержимое с переносом из поддерживаемого неконстантного контейнера.
Оператор << добавляет с переносом; он может изменить даже именованный источник без std::move. Для строковых литералов действуют отдельные перегрузки.

Пример без неоднозначных цепочек:

nx_array<QString> a{"X", "Y"};
nx_array<QString> b{"Z"};
a += "A";
a += "B";
a << b;
// a: {"X", "Y", "A", "B", "Z"}; b пуст.

Выражение "A" + QString("B") создаёт одну строку "AB", а не два элемента. Приоритет + выше приоритета <<. Сложные цепочки лучше разбивать на отдельные операции, особенно когда они изменяют источник.

11. Сырые указатели и удаление объектов

Публичный флаг isDeleteItemsOnDestroy по умолчанию равен false. Для поддерживаемых указателей на объекты значение true включает удаление адресуемых объектов в деструкторе nx_array. Флаг также участвует в проверках совместимости владения при копировании и переносе; он не заставляет обычные clear(), erase(), removeItem(), resize() и replace() удалять адресуемые объекты.

Явное удаление поддерживают:

clear_with_delete();
erase_with_delete(pos);
removeItem_with_delete(index);
removeRange_with_delete(start, finish = END_POS);
replace_with_delete(index, new_item);

Для поддерживаемых сырых указателей эти методы используют delete, а не delete[]. Они предназначены для объектов, допускающих такое удаление. Для неуказательных типов предусмотрено обычное удаление или замена значений.

Копирование сырых указателей поверхностное: адресуемые объекты не копируются. Конструктор копии nx_array не включает владение в копии. Такая копия содержит собственные элементы-указатели на те же внешние объекты и зависит от их времени жизни.

Нельзя назначать двух независимых владельцев одному объекту. Присваивание нового содержимого не удаляет объекты по прежним сырым указателям: если их требуется освободить, предварительно используйте clear_with_delete(). При выдаче указателей во внешний стандартный контейнер режим удаления nx_array не превращается в автоматическое владение этого контейнера. Для автоматического управления временем жизни обычно удобнее хранить std::unique_ptr<T> или std::shared_ptr<T>, учитывая требования вызываемых методов к копированию и перемещению.

12. Сложность и совместимость

Доступ по индексу, size(), empty(), capacity() — O(1).
Добавление одного элемента в конец — амортизированно O(1).
Вставка в начало/середину, удаление по индексу, moveItem(), swapItems(), extract() — в общем случае O(n): требуется проход по списку и/или изменение индекса указателей.
reverse() — O(n).
Присоединение m узлов к n элементам через spliceArray() в этой реализации требует O(n + m) на поддержание индекса, хотя сами объекты не копируются и не перемещаются.
sort() использует std::stable_sort над вспомогательным массивом итераторов и дополнительную память O(n); число сравнений определяется гарантиями std::stable_sort.

nx_array предоставляет знакомую часть интерфейса стандартных контейнеров, но не является полной заменой std::vector. Отличаются расположение данных, тип data(), некоторые возвращаемые значения и правила работы итераторов. Имеются прямые, константные и обратные итераторы. Требования к T зависят от вызываемого метода; в частности, конструктор с размером требует конструирования T по умолчанию даже при нулевом размере


четверг, 16 февраля 2023 г.

Проблема установки WinCC для TIA Portal из-за наличия уже установленной версии

 В продолжение предыдущего поста по поводу установки WinCC. Дошло до установки TIA Portal v15 при наличии уже установленной версии v13. Установщик на v15 общий - и для Step 7, и для WinCC, ну, и при старте выдает вот такое: 

"TIA Portal STEP 7 Professional V15.1 - WinCC Professional V15.1 cannot be operated in parallel with the currently installed version of 'WinCC Professional' or 'WinCC Runtime Professional'. Please remove..."

Тут история такая же, как и с SQL Server из предыдущего поста. Siemens опять развел панику на ровном месте, потому что на самом деле разные версии WinCC Flexible прекрасно уживаются в одной операционной системе. Соответственно, нужно опять подчистить конфигурационный файл установщика, чтобы снять эти ограничения. Файл тот же самый, что и в истории про SQLServer:

 \InstData\Resources\SIA2.ini

В нем находим все упоминания TermMessage9, TermMessage13, TermMessage16, TermMessage17, TermMessage31, TermMessage32, TermMessage36 и удаляем их. Далее установка проходит без эксцессов.

Ну, и если вы хотите быстро находить в установках Siemens файлы, где прописаны подобные ограничения (а это могут быть не только SIA2.ini), то рекомендую использовать бесплатную программу Searcher. Например, установщик требует что-то удалить (remove), так давайте по этому слову и найдём все текстовые файлы *.ini, где содержится слово "remove":

Вот, собственно, и всё.




среда, 15 февраля 2023 г.

Проблема установки WinCC для TIA Portal v11 из-за SQL Server 2005 / 2008

Как известно, старые версии TIA Portal актуальности не теряют (из-за отсутствия совместимости версий для внесения изменений и осуществления Upload). И тут возникла проблема с установкой WinCC Professional v11 из-за уже установленного SQL Server 2008. Установщих TIA Portal v11 устанавливает, помимо всего прочего, SQL Server 2005. Но по какой-то неизвестной причине Siemens решил, что обе версии SQL Server - 2005 и 2008 - не могут существовать в одной операционной системе.

 Чтобы отключить это ограничение, нужно отредактировать файл \InstData\Resources\SIA2.ini. В разделе TERMS нужно закомментировать (поставить ";" в начале строки) или удалить эти две строки:

{Registry};{HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\100\Tools\Setup\Version};{REGSZ};{NOTEXIST};{0};{TermMessage30};{0} 

{Registry};{HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\100\Tools\ClientSetup\CurrentVersion\CurrentVersion};{REGSZ};{NOTEXIST};{0};{TermMessage30};{0}