Как не возвращать дескрипторы внутренним объектам — Item 28 Effective Stack Overflow

Пункт 28 Эффективного C ++ говорит avoid returning "handles" to object internals, Этот вопрос показывает, как спроектировать ваш код, чтобы сделать именно это, думая об инкапсуляции, чтобы избежать случайного разоблачения внутренних элементов вашего класса.

Мой пример связан с массивом данных, и поскольку память — это проблема, которую я бы хотел избежать std::vector (и библиотека Boost).

Используя массив, вот очень упрощенная версия моего кода:

class Foo {
public:
Foo(int size) : data_(new int[size]) {
// Populate with data just for the example
for(int i = 0; i < size; i++) {
data_[i] = i;
}
}

// I know that this isn't good practice
int* const get_data() const {
return data_;
}

~Foo() {
delete[] data_;
}

private:
int* data_;
};

int main(void) {
Foo* foo = new Foo(10);
int* const data = foo->get_data();
delete foo;
// data now dangles which is bad!
return 0;
}

Я понимаю, что с помощью const с get_data() не делает это безопасным. Если бы я использовал вектор, я мог бы скопировать его, как в примере, приведенном выше, но, поскольку я хотел бы избежать этого, мне было интересно, как лучше спроектировать свой класс, чтобы избежать этой потенциально опасной ситуации?

0

Решение

Программирование и разработка программного обеспечения — это постоянный «выбор одной вещи над другой». Если вы действительно не можете позволить себе использовать std::vectorи вам нужно предоставить данные клиентскому коду, используя ваш класс, пусть будет так. Документируйте, почему это так, и удостоверьтесь, что это проверено с течением времени — возможно, когда ваша новая модель оборудования, имеющая 8 МБ ОЗУ вместо 2 МБ, сможет использовать дополнительные 4 байта, которые vector берет на себя ваш простой указатель.

Или напишите функцию доступа:

int& Foo::operator[](int pos)
{
// Add checks here to ensure `pos` is in range?
return data[pos];
}

Теперь вам не нужно возвращать указатель вообще …

3

Другие решения

Единственный способ в вашей ситуации — использовать умные указатели. Я бы предпочел shared_ptr. Вот как вы можете изменить свой класс, чтобы избежать проблем, которые вы поднимаете:

class Foo {
public:
Foo(int size) : data_(new int[size]) {
// Populate with data just for the example
for(int i = 0; i < size; i++) {
data_[i] = i;
}
}

// I know that this isn't good practice
std::shared_ptr<int> const get_data() const {
return data_;
}

~Foo() {
//delete[] data_;
}

private:
std::shared_ptr<int> data_;
};

int main(void) {
Foo* foo = new Foo(10);
std::shared_ptr<int> data = foo->get_data();
delete foo;
// data still good )) // --now dangles which is bad!--
return 0;
}
1

Существует множество случаев, когда возврат указателей является вполне приемлемым, даже если массив действительно содержался в std::vector; например использование внешних API и при копировании данных не приветствуется.

Я не вижу опасности, так как ты не программируешь для обезьян. Пользователь класса должен знать. Но я добавил функцию для запроса размера данных. Это для безопасности.

extern "C"{
void foobar( const int *data, unsigned int size );
}
class Foo {
public:
Foo(unsigned int size) : size_(size), data_(new int[size]) {
// Populate with data just for the example
for(unsigned int i = 0; i < size; i++) {
data_[i] = i;
}
}

unsigned int get_size() const{return size_;}
int* const get_data() const {
return data_;
}

~Foo() {
delete[] data_;
}

private:
int* data_;
unsigned int size_;
};

int main(void) {
Foo* foo = new Foo(10);
int* const data = foo->get_data();
foobar( data, foo->get_size() );
delete foo;
return 0;
}
0

учитывая, что даже стандартная библиотека имеет get_native_handle() на занятиях вроде std::thread Интересно, кто между стандартной библиотекой или EC ++ был не прав?

Эффективный C ++ — это набор рекомендаций, которые вы просто не можете применять в любом контексте. Это «моральная» книга. Не рассматривайте это как «законный». Если вам необходимо предоставить данные, просто сделайте это.

В конце концов, тот, кто удаляет указатель на ваши внутренние данные (после получения через геттер), также делает «плохую вещь», так как он действует разрушительно на что-то, не принадлежит ему.

0

Если вы действительно хотите избежать использования std :: vector, вы можете реализовать подход, аналогичный тому, который используется некоторыми функциями winapi.

size_t get_data(int* data_ptr, size_t& buffer_size)
{
if(buffer_size < sizeof (data_)
{
buffer_size = sizeof(data);
return 0;
}
//You could you memcpy for ints here
for(int i = 0; i < size; i++) {
data_ptr[i] = i;
}
return sizof(data_);
}

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

0
По вопросам рекламы [email protected]