Обнаружить изменение переменной во время выполнения в C / C ++ - PullRequest
5 голосов
/ 09 марта 2010

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

Вот фрагмент кода, модифицированный для этого примера:

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 и проверьте, что все значения изменились.

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

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

Ответы [ 10 ]

5 голосов
/ 09 марта 2010

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

3 голосов
/ 09 марта 2010

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

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

Сейчас:

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

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

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

3 голосов
/ 09 марта 2010

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

2 голосов
/ 09 марта 2010

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

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

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

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

1 голос
/ 09 марта 2010

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

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

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

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

1 голос
/ 09 марта 2010

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

  • Заблокировать прямой доступ к m_arr, сделав его приватным
  • добавить массив bool того же размера, что и 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 () на функтор или добавить поддержку функции обратного вызова. Это зависит от ваших предпочтений.

// последнее редактирование, чтобы сделать его корректным

1 голос
/ 09 марта 2010

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

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

0 голосов
/ 10 марта 2010

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

0 голосов
/ 09 марта 2010

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

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

0 голосов
/ 09 марта 2010

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

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

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

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