Я разрабатываю библиотеку в 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]
этот элемент не инициализируется и мог вызвать проблемы в будущих вычислениях. Это - то, что я хотел бы проверить.
Я не вижу прямого способа сделать это в C++. То, что вы собираетесь реализовать, - это фильтры в Ruby on Rails, где перед обращением к любому методу вызываются фильтры.
В качестве альтернативы вы можете обернуть ваш массив в структуру и внутри этой структуры перегрузить оператор [] как для присвоения, так и для доступа.
Теперь:
1) Внутри перегруженного оператора [] для присвоения ведите счетчик и увеличивайте его при каждой инициализации.
2) Внутри перегруженного оператора доступа [] перед обращением к содержимому массива проверьте счетчик, равен ли он общему количеству элементов. Если нет, выбросьте ошибку.
Надеюсь, это поможет.
Если эффективность не является одной из ваших основных целей, вы можете
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
Для меня это не столько похоже на C ++, сколько на C, с парой полезных функций, таких как конструкторы. Ваш класс не может решить, является ли он классом C-struct или C ++, и я думаю, что это часть проблемы, с которой вы столкнулись с инициализацией здесь.
Как насчет того, чтобы сделать это по-другому, сделав массив закрытым и предоставив невиртуальный интерфейс инициализации, который принимает функтор наподобие std :: generate. Затем вы вызываете функтор один раз для каждого элемента. Таким образом, вы узнаете, вызвали ли они ваш инициализатор, и вы знаете, что все элементы безопасно инициализированы. Мало того, они затем защищены от дочерних классов, меняющих их, когда захотят.
Если по той или иной причине вам необходимо сохранить текущий подход, использование NaN или inf может сработать, если вы можете гарантировать, что эти значения не будут перехватывать исключение на любом оборудовании, для которого вы планируете выпустить библиотеку. Более безопасно, просто выберите какое-нибудь нейтральное значение, такое как 0 или один, и, если клиент не сможет инициализировать, просто ясно дайте понять, что он стреляет себе в ногу.
Другими словами, сделать эти данные общедоступными и обеспечить их правильную инициализацию - это две (почти) взаимоисключающие цели.
Я думаю, вы насвистываете по этому поводу. Вы не можете диктовать, что все значения правильно заполнены. В лучшем случае все, что вы можете сделать, это указать, что пользователь вставляет какое-то значение в элементы массива. Если вы вставляете NaN в массив, который затем проверяете работоспособность, все ваши пользователи будут инициализировать нулем, -1 или MAX_DOUBLE. Так что вы могли бы просто предоставить им ctor, чтобы сделать это и покончить с этим.
Мой производный класс вполне может использовать базовый класс для резервирования 1000 элементов, но он может содержать собственный счетчик того, сколько элементов он фактически использовал.
Это зависит от того, что вы пытаетесь сделать: действительно ли вам нужно создавать массив в базовом классе, или вы можете использовать предложение Нила push_back ()
?
В любом случае, рассмотрите возможность использования std :: vector <>
(зачем вам собственное управление памятью?) boost :: optional
. (Вы знаете о библиотеках повышения ?)
Кроме того, вы действительно хотите разделить создание объекта на две фазы? Любой клиентский код, который создает потомок BaseClass
, должен вызывать initArray ()
. Вас беспокоит эффективность?
Ваша идея использования inf или NaN подойдет. Любой предопределенный инициализатор будет работать достаточно хорошо.
Другая идея - создать функцию-член доступа для изменения элементов. В методе доступа вы должны установить измененный флаг, в InitArray () вы должны сбросить измененный флаг.
Если вы пытаетесь создать "надежный" интерфейс, а не думаете о 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, как предполагаемый размер.)
Вы также можете установить точку прерывания данных через регистры отладки. Эта ссылка есть информация по теме.
Почему бы не использовать std::vector? Тогда конечный пользователь будет добавлять в него данные с помощью push_back, а вы сможете проверить размер, чтобы узнать, сколько элементов было добавлено.
Вот мое предложение:
Добавьте еще один защищенный виртуальный метод к базовому классу, который называется «InitArrayImpl», и прикажите создателю подкласса заполнить им массив.
Метод initArray должен быть общедоступным, не виртуальным, в этом методе вызовите InitArrayImpl, перед вызовом инициализируйте массив недопустимыми значениями, после вызова проверьте, все ли значения были изменены.
Клиент класса должен быть доступен только для метода initArray.