На мысль навела
статья.
Вот в C++ принято что нет проверки выхода за пределы массива. Т.е. обратились по индексу 6, в то время как элементов всего 6 — вам даже не сообщат об ошибке. Считается что C++-разработчики не могут допустить таких ошибок и если ты допустил — то вон из профессии, таким как ты тут не место (ну или хотя бы никому не говори об этом).
Добавлю оговорку. Есть at(), который как бы на отвяжись добавили. Однако же он не решает проблему, т.к. даже при банальном копировании данных он вызываться не будет (см. пример ниже).
Это можно оправдать скоростью работы, ведь каждая проверка требует доп. инструкций процессора. Было бы неплохо иметь возможность хотя бы применять такие проверки по некой опции, к примеру добавить флаг сборки или что-то подобное. Когда уже отладил — флаг можно и убрать, в принципе. Более того — скорость не всегда критична и не всегда на первом месте — иногда важнее точность работы кода и быстрое обнаружение ошибок.
И вопрос такой. В принципе то C++ не виноват, он же не запрещает проверки выхода за пределы. Однако же виновата философия, которая говорит что такие проверки нужны только школьникам, что настоящие программисты не ошибаются. И я подумал грешным делом — а что если просто добавить обертки для стандартных классов, которые выполняют такие проверки? Не слишком ли смелое решение?
Как оказалось, дело это не такое уж тривиальное. Вот, к примеру, GPT выдал для проверки c std::ranges — но это не всегда работает как нужно:
| Скрытый текст |
| #include <iostream>
#include <stdexcept>
#include <ranges>
#include <algorithm>
#include <iterator>
#include <concepts>
#include <vector>
template<typename T>
class SafeArray
{
public:
using value_type = T;
using difference_type = std::ptrdiff_t;
class Iterator
{
public:
using iterator_concept = std::random_access_iterator_tag;
using iterator_category = std::random_access_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
Iterator() : ptr_(nullptr), begin_(nullptr), end_(nullptr) {}
Iterator(T* ptr, T* begin, T* end) : ptr_(ptr), begin_(begin), end_(end) {}
// Dereference with bounds checking
reference operator*() const
{
if (ptr_ < begin_ || ptr_ >= end_)
{
throw std::out_of_range("Iterator out of bounds on dereference");
}
return *ptr_;
}
pointer operator->() const
{
return &**this;
}
// Increment and decrement with bounds checking
Iterator& operator++()
{
if (ptr_ + 1 > end_)
{
throw std::out_of_range("Iterator goes beyond array bounds on increment");
}
++ptr_;
return *this;
}
Iterator operator++(int)
{
Iterator tmp = *this;
++(*this);
return tmp;
}
Iterator& operator--()
{
if (ptr_ - 1 < begin_)
{
throw std::out_of_range("Iterator goes beyond array bounds on decrement");
}
--ptr_;
return *this;
}
Iterator operator--(int)
{
Iterator tmp = *this;
--(*this);
return tmp;
}
// Arithmetic operations with bounds checking
Iterator operator+(difference_type n) const
{
T* new_ptr = ptr_ + n;
if (new_ptr < begin_ || new_ptr > end_)
{
throw std::out_of_range("Iterator goes beyond array bounds on addition");
}
return Iterator(new_ptr, begin_, end_);
}
Iterator operator-(difference_type n) const
{
T* new_ptr = ptr_ - n;
if (new_ptr < begin_ || new_ptr > end_)
{
throw std::out_of_range("Iterator goes beyond array bounds on subtraction");
}
return Iterator(new_ptr, begin_, end_);
}
difference_type operator-(const Iterator& other) const
{
return ptr_ - other.ptr_;
}
Iterator& operator+=(difference_type n)
{
*this = *this + n;
return *this;
}
Iterator& operator-=(difference_type n)
{
*this = *this - n;
return *this;
}
reference operator[](difference_type n) const
{
return *(*this + n);
}
// Comparison operators
bool operator==(const Iterator& other) const
{
return ptr_ == other.ptr_;
}
bool operator!=(const Iterator& other) const
{
return ptr_ != other.ptr_;
}
bool operator<(const Iterator& other) const
{
return ptr_ < other.ptr_;
}
bool operator>(const Iterator& other) const
{
return ptr_ > other.ptr_;
}
bool operator<=(const Iterator& other) const
{
return ptr_ <= other.ptr_;
}
bool operator>=(const Iterator& other) const
{
return ptr_ >= other.ptr_;
}
private:
T* ptr_;
T* begin_;
T* end_;
};
using iterator = Iterator;
using const_iterator = Iterator;
SafeArray(std::size_t size) : size_(size), data_(new T[size]) {}
~SafeArray()
{
delete[] data_;
}
T& operator[](std::size_t index)
{
if (index >= size_)
{
throw std::out_of_range("Index out of bounds");
}
return data_[index];
}
const T& operator[](std::size_t index) const
{
if (index >= size_)
{
throw std::out_of_range("Index out of bounds");
}
return data_[index];
}
iterator begin()
{
return Iterator(data_, data_, data_ + size_);
}
iterator end()
{
return Iterator(data_ + size_, data_, data_ + size_);
}
const_iterator begin() const
{
return Iterator(data_, data_, data_ + size_);
}
const_iterator end() const
{
return Iterator(data_ + size_, data_, data_ + size_);
}
std::size_t size() const
{
return size_;
}
private:
std::size_t size_;
T* data_;
};
int main()
{
SafeArray<int> safeArray(5);
// Initialize the array
for (std::size_t i = 0; i < safeArray.size(); ++i)
{
safeArray[i] = static_cast<int>(i + 1);
}
std::vector<int> output(5, 0); // Vector to copy data
try
{
// Attempt to copy all elements from safeArray to output
std::ranges::copy(safeArray.begin(), safeArray.end(), output.begin());
// Display copied data
for (const auto& val : output)
{
std::cout << val << " ";
}
std::cout << std::endl;
// Attempt to copy 8 elements, which goes beyond array bounds
std::ranges::copy(safeArray.begin(), safeArray.end() + 1, output.begin());
}
catch (const std::out_of_range& e)
{
std::cerr << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
|
| |
Ну и сам вопрос. Может кто-то уже думал в этом направлении и есть такие безопасные обертки?