Обнаружьте модификацию переменной во времени выполнения в C/C++

Я разрабатываю библиотеку в C++, где пользователи/программист расширят класс BaseClass это имеет метод initArray. Этот метод должен быть реализован пользователем/программистом, и он должен обычно инициализировать все элементы массива m_arr.

Вот snipplet, измененный к этому примеру:

class BaseClass {
     public:

     BaseClass(int n) {
         m_arr = new double[n]; 
         size = n;
     };
     virtual ~BaseClass();

     int size;
     double* m_arr;

     virtual int initArray();
};

Иногда, пользователь/программист реализует a initArray это не инициализирует некоторые элементы m_arr. То, что я хотел бы, должно создать функцию в моей библиотеке, которая проверяет если initArray действительно инициализировал все элементы m_arr. Эта функция должна быть вызвана проверкой работоспособности rutine во времени выполнения.

Мой вопрос: действительно ли возможно обнаружить изменения на этом массиве? Я могу только думать об инициализации массива с некоторыми недопустимыми значениями (как NaN или Inf), звонить initArray и проверьте, что все значения изменились.

Спасибо за Ваши идеи,

David


Править

Вот пример клиентского кода, который я пытаюсь обнаружить:

// .h:
class MyExample : public BaseClass {
     public:
     MyExample();
     virtual ~MyExample();
     virtual int initArray();
};

// .cpp:
MyExample::MyExample() : BaseClass(3) { }
MyExample::~MyExample() { }
int MyExample::initArray() {
     m_arr[0] = 10;
     //m_arr[1] = 11; // let's say someone forgot this line
     m_arr[2] = 12;
     return 0;
}

Так, путем упущения m_arr[1] этот элемент не инициализируется и мог вызвать проблемы в будущих вычислениях. Это - то, что я хотел бы проверить.

5
задан YuppieNetworking 9 March 2010 в 14:41
поделиться

10 ответов

Я не вижу прямого способа сделать это в C++. То, что вы собираетесь реализовать, - это фильтры в Ruby on Rails, где перед обращением к любому методу вызываются фильтры.

В качестве альтернативы вы можете обернуть ваш массив в структуру и внутри этой структуры перегрузить оператор [] как для присвоения, так и для доступа.

Теперь:

1) Внутри перегруженного оператора [] для присвоения ведите счетчик и увеличивайте его при каждой инициализации.

2) Внутри перегруженного оператора доступа [] перед обращением к содержимому массива проверьте счетчик, равен ли он общему количеству элементов. Если нет, выбросьте ошибку.

Надеюсь, это поможет.

3
ответ дан 18 December 2019 в 10:44
поделиться

Если эффективность не является одной из ваших основных целей, вы можете

  • заблокировать прямой доступ к m_arr , сделав его частным
  • добавить массив логических значений того же размера, что и m_arr m_field_initialized = new bool [n] и инициализировать его ложными значениями.
  • Создайте открытый (или защищенный) экземпляр класса доступа, который будет заключать в оболочку m_arr и m_field_initialized
  • Аксессор должен иметь метод setVal (int i, double val) , который установит m_arr [i] и дополнительный m_field_initialized [i] флаг в true ;
  • В вашем методе проверки инициализации проверьте, все ли поля из m_field_initialized были установлены в значение true.

Вы можете улучшить это, предоставив метод для более быстрого прямого доступа при соблюдении условий инициализации. Он должен вернуть нулевой указатель перед проверкой инициализации, а после успешной инициализации он вернет указатель на ваш массив.

double * directAccess() {
   if (m_arr_initialized) return m_arr;
   else return 0;
}

m_arr_initialized должен быть установлен вашим методом проверки инициализации.


Если не требуется, чтобы массив выделялся в базовом классе, вы можете установить m_arr равным нулю, оставить выделение для подклассов и просто проверить, установлен ли указатель m_arr на ненулевой. Дополнительно может быть установлено допустимое поле, обозначающее выделенный размер.Или вы можете предварительно заблокировать доступ к m_arr и предоставить метод в базовом классе для выделения allocate (std :: size_t size) или выделения с начальным значением allocate (std :: size_t size, double initVal ) или даже принудительно передать функцию инициализации allocate (std :: size_t size, double (* callback) (std :: size_t element)) , которая будет вызываться для каждого элемента. Есть много возможностей.


edit: после вашего редактирования я предлагаю указатель (или ссылку) либо на объект инициализации, либо на обратный вызов функции в конструкторе BaseClass. Это заставит подклассы предоставить код инициализации. Примите во внимание следующее:

class InitializerInterface
{
   public:
     virtual double get(int element) const = 0; 
}

В конструкторе базового класса

 BaseClass(int n, const InitializerInterface & initializer) {
     m_arr = new double[n]; 
     size = n;
     for (int i = 0; i < n; i++)
        m_arr[i] = initializer.get(i);
 };

Теперь любой подкласс должен передать некоторую инициализацию конструктору. Вы, конечно, можете заменить метод get () функтором или добавить поддержку функции обратного вызова. Все зависит от ваших предпочтений.

// последнее изменение, чтобы сделать его const-corrective

1
ответ дан 18 December 2019 в 10:44
поделиться

Для меня это не столько похоже на C ++, сколько на C, с парой полезных функций, таких как конструкторы. Ваш класс не может решить, является ли он классом C-struct или C ++, и я думаю, что это часть проблемы, с которой вы столкнулись с инициализацией здесь.

Как насчет того, чтобы сделать это по-другому, сделав массив закрытым и предоставив невиртуальный интерфейс инициализации, который принимает функтор наподобие std :: generate. Затем вы вызываете функтор один раз для каждого элемента. Таким образом, вы узнаете, вызвали ли они ваш инициализатор, и вы знаете, что все элементы безопасно инициализированы. Мало того, они затем защищены от дочерних классов, меняющих их, когда захотят.

Если по той или иной причине вам необходимо сохранить текущий подход, использование NaN или inf может сработать, если вы можете гарантировать, что эти значения не будут перехватывать исключение на любом оборудовании, для которого вы планируете выпустить библиотеку. Более безопасно, просто выберите какое-нибудь нейтральное значение, такое как 0 или один, и, если клиент не сможет инициализировать, просто ясно дайте понять, что он стреляет себе в ногу.

Другими словами, сделать эти данные общедоступными и обеспечить их правильную инициализацию - это две (почти) взаимоисключающие цели.

1
ответ дан 18 December 2019 в 10:44
поделиться

Я думаю, вы насвистываете по этому поводу. Вы не можете диктовать, что все значения правильно заполнены. В лучшем случае все, что вы можете сделать, это указать, что пользователь вставляет какое-то значение в элементы массива. Если вы вставляете NaN в массив, который затем проверяете работоспособность, все ваши пользователи будут инициализировать нулем, -1 или MAX_DOUBLE. Так что вы могли бы просто предоставить им ctor, чтобы сделать это и покончить с этим.

Мой производный класс вполне может использовать базовый класс для резервирования 1000 элементов, но он может содержать собственный счетчик того, сколько элементов он фактически использовал.

0
ответ дан 18 December 2019 в 10:44
поделиться

Это зависит от того, что вы пытаетесь сделать: действительно ли вам нужно создавать массив в базовом классе, или вы можете использовать предложение Нила push_back () ?

В любом случае, рассмотрите возможность использования std :: vector <> (зачем вам собственное управление памятью?) boost :: optional . (Вы знаете о библиотеках повышения ?)

Кроме того, вы действительно хотите разделить создание объекта на две фазы? Любой клиентский код, который создает потомок BaseClass , должен вызывать initArray () . Вас беспокоит эффективность?

0
ответ дан 18 December 2019 в 10:44
поделиться

Ваша идея использования inf или NaN подойдет. Любой предопределенный инициализатор будет работать достаточно хорошо.

Другая идея - создать функцию-член доступа для изменения элементов. В методе доступа вы должны установить измененный флаг, в InitArray () вы должны сбросить измененный флаг.

1
ответ дан 18 December 2019 в 10:44
поделиться

Если вы пытаетесь создать "надежный" интерфейс, а не думаете о initArray () как о подпрограмме с побочными эффектами ... почему бы не стилизовать его под что-то более функциональное? initArray () может отвечать за выделение и построение вектора, а также передавать обратно ссылку на этот полностью сконструированный объект. Это также позволит вам сделать m_arr закрытым:

class BaseClass {
private:
    size_t size;
    auto_ptr< vector< double > > m_arr;

public:
    BaseClass(size_t n) {
        size = n;
     };
     virtual ~BaseClass();

protected:
     virtual auto_ptr< vector< double > > initArray() const;
};

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

3
ответ дан 18 December 2019 в 10:44
поделиться

Вы также можете установить точку прерывания данных через регистры отладки. Эта ссылка есть информация по теме.

0
ответ дан 18 December 2019 в 10:44
поделиться

Почему бы не использовать std::vector? Тогда конечный пользователь будет добавлять в него данные с помощью push_back, а вы сможете проверить размер, чтобы узнать, сколько элементов было добавлено.

5
ответ дан 18 December 2019 в 10:44
поделиться

Вот мое предложение:

Добавьте еще один защищенный виртуальный метод к базовому классу, который называется «InitArrayImpl», и прикажите создателю подкласса заполнить им массив.

Метод initArray должен быть общедоступным, не виртуальным, в этом методе вызовите InitArrayImpl, перед вызовом инициализируйте массив недопустимыми значениями, после вызова проверьте, все ли значения были изменены.

Клиент класса должен быть доступен только для метода initArray.

2
ответ дан 18 December 2019 в 10:44
поделиться