Какой умный способ использовать новый оператор C ++ с функцией? - PullRequest
2 голосов
/ 15 июня 2011

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

Метод 1:

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

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

Минусы: нужно заранее знать размер изображения.

// Data allocated outside the image, allocated space passed to function. This works.
// Notice that width & height are passed to the function. 
size=(*width)*(*height);
image  = new unsigned char[size];

void read_pgm(unsigned char *image, char *file_name, int width, int height){

    // Code to read sizeof(char)*width*height bytes of data from the file into image

}

Метод 2:

Я подумал, что было бы неплохо, чтобы функция распределяла свои собственные данные так,Мне не нужно передавать его размер.Если бы я попытался выделить для него место в функции, оно, похоже, было потеряно после завершения функции.В следующем случае функция read_pgm работает нормально, но если я затем пытаюсь записать эти данные в другой файл, мой код вылетает.

Плюсы: не нужно заранее знать размер изображения,нет необходимости выделять данные в вызывающем коде.

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

// Data allocated inside the image for a pointer passed to the function. This doesn't work.
void read_pgm(unsigned char *image, char *file_name, int *width, int *height){

    size=(*width)*(*height);
    image  = new unsigned char[size];

    // Code to read the data from the file into image

}

Метод 3:

Здесьданные снова размещаются в функции, но возвращаются как возвращаемый элемент.Это работает, то есть я могу записать данные в другое изображение в порядке.Я не понимаю, почему это работает, а метод 2 - нет.

Плюсы: те же, что и в методе 2.

Минусы: те же, что и в методе 2, за исключением того, что в настоящее время это работает.

// Data allocated in the function, and returned. This works.
unsigned char* read_pgm(char *file_name, int *width, int *height){

    // Allocate data for the image
    size=(*width)*(*height);
    image  = new unsigned char[size];

    // Code to read the data from the file into image

    return image; // Return pointer to the data
}

Итак, мои вопросы:

  1. В таком случае разумнее настроить функцию так, чтобы она сама выделяла пространство, чтобы вызывающий код нене нужно предоставить размер изображения?Или же разумнее выделить вне функции в качестве напоминания, что удаление должно быть вызвано на изображении в какой-то момент.Или я ошибаюсь, думая, что это нужно сделать?Кажется, что вызов метода 2 или метода 3 в цикле приведет к утечке памяти.

  2. Почему не работает метод 2?

Спасибо.

Ответы [ 7 ]

5 голосов
/ 15 июня 2011

Если вы хотите узнать умный способ, то ответ должен быть «Ничего из вышеперечисленного».Умный способ?Используйте вектор.Вот для чего это.Потому что использование new напрямую отстой.Управление границами собственной памяти - отстой.У нас есть занятия для этого.И char* для строки?По крайней мере, сделать это const char*.const std::string& лучше.Я также должен спросить - какой формат изображения вы пытаетесь прочитать, который не хранит ширину и высоту изображения в формате файла?Мне кажется, что вам лучше почитать это из файла.

std::vector<unsigned int> void ReadImage(const std::string& filename, int width, int height) {
    std::vector<unsigned int> imageData(width * height);
    // Read here from filestream
}

std::vector<unsigned int> imageData = ReadImage("ohai.png", 1000, 600);

Вам нужно узнать о - правильность const, RAII и стандартная библиотека.

1 голос
/ 15 июня 2011

Ответ на вопрос 2

Чтобы заставить его работать, вы должны передать ссылку на указатель

unsigned char * & image

В противном случае вы выделяете память, а КОПИЯ переданного указателя указывает на нее,Исходный объект указателя не изменяется.

Ответ на вопрос 1

Когда-либо слышали о умных указателях ?

Умные указатели могут использоваться для манипулированияпамять сама по себе.Если вы не хотите использовать умные указатели по какой-то необъяснимой причине, например, по псевдооптимизации, то, я думаю, вы сами описали все плюсы и минусы всех методов - всегда есть компромисс.Вам решать

0 голосов
/ 15 июня 2011

Рассмотрим инкапсуляцию всех функций, связанных с чтением и «обработкой» данных в классе.

В конструкторе должен быть указан путь к файлу как std :: string.

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

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

Деструктор класса будет отвечать за закрытие дескриптора файла (что можно сделать раньше, если необходимо) и отмена выделения данных.

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

0 голосов
/ 15 июня 2011

На самом деле есть действительно отличный раздел в C ++ FAQBook по этим вопросам.

Большая проблема, как вы говорите, заключается в том, чтобы гарантировать, что память распределена таким образом, чтобы она не протекала. В C ++ есть множество удобных средств, наиболее очевидными из которых являются new и delete.

Самое простое - выделить хранилище с new в контексте, в котором оно выйдет за пределы области видимости. Итак ...

 {
     MyData *md = new MyData(args);

     doSomething(md);

 }

теперь, когда md выходит из области видимости, он автоматически вызывает dtor. Теперь этот трюк будет работать практически везде.

С вашим методом 2, вы правы, что написано как есть, это утечка памяти; в следующий раз, когда вы сделаете new ссылка на этот последний раз будет потеряна, но она не будет завершена или уничтожена.

Решение состоит в том, чтобы явно вызвать dtor, т. Е. Удалить его где-нибудь еще.

Я подозреваю, что причина, по которой он не работает, заключается в том, что image - это указатель, который хочет вернуть новый. но поскольку C ++ выполняет указатели по значению вызова, вы помещаете этот адрес в локальную память стека, и он исчезает.

Вы либо хотите **, либо, что еще лучше, ссылку.

Третье решение состоит в том, что в C ++ есть типы указателей, помогающие управлять свободным магазином. Книга часто задаваемых вопросов описывает, как реализовать указатели подсчета ссылок здесь .

0 голосов
/ 15 июня 2011

Все, что вы делаете в настоящее время, приводит к утечке памяти, поскольку вы никогда не delete[] ничего.

Прежде всего, да, сделайте размеры переменными. Во-вторых, либо верните указатель на только что выделенное хранилище кучи и не забудьте удалить его в вызывающем , либо верните умный контейнер объект.

A std::vector<unsigned char> будет хорошо:

std::vector<unsigned char> get_image(const std::string & filename, size_t & width, size_t & height)
{
  // determine width and height

  /* ... */

  std::vector<unsigned char> result(width * height, 0);

  // read into &result[0], vector guarantees contiguous storage

  return result;
}
0 голосов
/ 15 июня 2011

"2. Почему метод 2 не работает?"

Потому что вы передаете указатель копией, а не ссылкой. Вы передаете указатель, а затем запрашиваете повторную инициализацию , где этот указатель указывает на с вашим вызовом new. Когда достигается область действия функции, то и жизнь локальной переменной image, а также место, куда ушло все это прекрасное новое хранилище.

Что вы действительно хотите сделать (в данном случае), это передать либо ссылку на указатель, либо указатель на указатель, чтобы вы могли изменить значение переданного аргумента (в данном случае указатель) и получить его изменения будут видны вне области действия функции.

void read_pgm(unsigned char **image, char *file_name, int *width, int *height)
{      
    size=(*width)*(*height);     
    (*image)  = new unsigned char[size];      

    // Code to read the data from the file into image  
} 
0 голосов
/ 15 июня 2011

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

Что касается другого вопроса: на самом деле это просто вопрос четкого документирования, кто несет ответственность за удаление выделенных данных. (Кстати: да, вы должны освободить, иначе вы будете пропускать). Во многих API вы увидите в документации: «вызывающий абонент обязан удалить это» или «вызовите эту другую функцию для удаления выделенных данных».

Хороший способ отделить интересы такого рода - использовать умные указатели (как предлагает Армен).

...