2010-03-09 8 views
5

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

Вот snipplet, модифицированный в этом примере:

class BaseClass { 
    public: 

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

    int size; 
    double* m_arr; 

    virtual int initArray(); 
}; 

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

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

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

Дэвид


Редактировать

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

// .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] этот элемент не инициализирован и может вызвать проблемы при будущих вычислениях. Это то, что я хотел бы проверить.

+6

Несомненно, сделайте m_arr частным и предоставите аксессуар. –

ответ

3

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

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

Сейчас:

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

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

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

+0

Мне нравится этот ответ, потому что он остается со структурой, предложенной на примере. – YuppieNetworking

+0

m_arr [0] = 10; m_arr [0] = 11; // Ошибка, ошибка копирования-вставки! m_arr [2] = 12; –

+0

@Mark спасибо, что указал. Вы можете добавить условие до приращения счетчика, чтобы проверить, не инициализирован ли текущий элемент – mukeshkumar

5

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

+0

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

+0

@YuppieNetworking Вы можете использовать векторы с кодом C. – 2010-03-09 14:11:58

+0

@Neil Butterworth Обратите внимание, что std :: vector перераспределяется на некоторые изменения размера, а также могут меняться места расположения элементов памяти. Это иногда критично. – doc

1

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

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

+1

Идея wit NaNs будет работать до тех пор, пока подкласс не инициализирует массив с помощью NaNs самостоятельно. К сожалению, это может быть частью функциональности подкласса. Представьте себе, что подкласс принимает два параметра 'a, b', а начальное значение -' a/b'. Результирующее NaN (например, в случае a = 0 и b = 0) является частью его спецификации. Или, с другой стороны, инициализация может быть настолько сложной, что она непреднамеренно может вернуть NaN из-за ошибок округления. Возможные ошибки, которые могут возникнуть при таком подходе, могут быть очень трудными для отслеживания. – doc

+0

@doc: отличная точка – YuppieNetworking

0

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

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

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

+0

Эта конструкция связана с тем, что BaseClass на самом деле является объектом с состояниями (например, в симуляции). initArray инициализирует состояние объекта, и его можно инициализировать несколько раз по мере изменения состояний. – YuppieNetworking

+0

'boost :: optional' отлично, но нельзя безопасно подвергать C-коду. –

2

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

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

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

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

+0

void Base :: initArray() {initArrayWithInvalidValue(); initArrayImpl(); checkArray(); } где initArrayImpl() является виртуальной функцией. – Corwin

+0

Да, мои первоначальные мысли были в этом направлении. Вот почему я спросил вас, ребята, так как это, вероятно, можно было бы сделать другим способом. – YuppieNetworking

+0

Это идиоматический путь. Действительно, если вы слушаете совет Херба Саттера (и это похоже на то, что он знает что-то правильно?), Интерфейсы никогда не должны выставлять виртуальные методы. не виртуальные методы, вызывающие частные виртуальные методы, позволяют проверять предварительные и пост-условия в базовом классе. –

3

Если вы пытаетесь сделать «надежный» интерфейс, а не думать о 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 в качестве заданного размера.)

+0

Я согласен с вашим функциональным подходом. Думаю, я пойду так, потому что мне нравится рутина без побочных эффектов. Однако, как я сказал Нилу, у меня есть много пользователей/программистов, которые уже кодируют initArray и его побочные эффекты, поэтому я надеялся улучшить некоторые подробности об этой библиотеке с низким влиянием на их привычки. – YuppieNetworking

0

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

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

1

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

  • Блок прямой доступ к m_arr, сделав его частным
  • добавить массив BOOLS одного и того же размера, как m_arr m_field_initialized = new bool[n] и инициализировать его с ложные значения.
  • создать общедоступный (или защищенный) экземпляр класса аксессора, который будет обернуть m_arr и m_field_initialized
  • Accessor должен иметь 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)), которая будет вызываться для каждого элемента. Есть много возможностей.


редактировать: после редактирования я предлагаю указатель (или ссылку) на любой объект инициализации или обратный вызов для функции в 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-правильно

1

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

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

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

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

+0

Похоже на C + пару функций, потому что код сильно обрезается. Я согласен с точкой вашего NaN. – YuppieNetworking

0

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

+0

Спасибо, но я ищу решение для выполнения, без отладчиков. – YuppieNetworking

Смежные вопросы