c ++ 11 — утечка памяти с использованием исключений C ++

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

К сожалению, я не понимаю, как исключения влияют на поведение утечки памяти в программе на C ++, потому что Valgrind говорит мне, что в следующем коде есть 2 утечки памяти:

file.h

#ifndef FILE_H
#define FILE_H

#include <iostream>
#include <memory>
#include <string>

#include <stdio.h>

class FileDeleter {
public:
void operator()(FILE *p);
};

class File {
public:
File(const std::string path);

private:
const std::string _path;
std::unique_ptr<FILE, FileDeleter> _fp;

};

#endif // FILE_H

file.cpp

#include <cstring>
#include <iostream>
#include <stdexcept>
#include <utility>

#include <stdio.h>

#include "file.h"
void FileDeleter::operator()(FILE *p)
{
if (!p)
return;

if (fclose(p) == EOF)
std::cerr << "FileDeleter: Couldn't close file" << std::endl;
}

File::File(const std::string path)
: _path(std::move(path)),
_fp(fopen(_path.c_str(), "a+"))
{
if (!_fp)
throw std::runtime_error("Couldn't open file");
}

main.cpp

#include <cstdlib>
#include "file.h"
int main()
{
File my_file("/root/.bashrc");

return EXIT_SUCCESS;
}

Я решил открыть /root/.bashrc специально для того, чтобы на самом деле File ctor выдал исключение. Если я не брошу туда, Вальгринд будет совершенно счастлив.
Что мне здесь не хватает при использовании исключений?
Как вы реализуете простой дескриптор файла «правильно» (исключение безопасно)?

Заранее спасибо!

Примечание: операции чтения / записи все еще отсутствуют, так как я уже борюсь с основами.

Редактировать № 1:
Это фактический вывод Valgrind, использующий —leak-check = full:

==7998== HEAP SUMMARY:
==7998==     in use at exit: 233 bytes in 3 blocks
==7998==   total heap usage: 5 allocs, 2 frees, 817 bytes allocated
==7998==
==7998== 38 bytes in 1 blocks are possibly lost in loss record 1 of 3
==7998==    at 0x4C27CC2: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7998==    by 0x4EEC4F8: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x4EEDC30: char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x4EEE047: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x40137D: main (in ../a.out)
==7998==
==7998== 43 bytes in 1 blocks are possibly lost in loss record 2 of 3
==7998==    at 0x4C27CC2: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7998==    by 0x4EEC4F8: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x4EEDC30: char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x4EEE047: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x400EBF: File::File(std::string) (in /home/frank/3Other/Code/Laboratory/c++/c++namedpipe/a.out)
==7998==    by 0x401390: main (in ../a.out)
==7998==
==7998== 152 bytes in 1 blocks are possibly lost in loss record 3 of 3
==7998==    at 0x4C27730: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7998==    by 0x4E8F8F2: __cxa_allocate_exception (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x400E9B: File::File(std::string) (in /home/frank/3Other/Code/Laboratory/c++/c++namedpipe/a.out)
==7998==    by 0x401390: main (in ../a.out)
==7998==
==7998== LEAK SUMMARY:
==7998==    definitely lost: 0 bytes in 0 blocks
==7998==    indirectly lost: 0 bytes in 0 blocks
==7998==      possibly lost: 233 bytes in 3 blocks
==7998==    still reachable: 0 bytes in 0 blocks
==7998==         suppressed: 0 bytes in 0 blocks

Редактировать № 2:
Исправлено исключение, генерируемое в Destructor.

Редактировать № 3:
Удален класс FileException, вместо него используется std :: runtime_error.

Изменить № 4:
Добавлена ​​проверка NULL в удалителе.

0

Решение

Я вижу следующие проблемы:

  1. fclose(NULL) не допускается …
    • но к счастью std::unique_ptr не вызывает удалитель, если get() == nullptrтак что это должно быть хорошо здесь
  2. FileDeleter:operator() вызывается из деструктора, что означает это никогда не должно бросать. В вашем конкретном случае любая ошибка, возвращенная из fclose, приведет к std::terminate быть названным
  3. Ваше исключение должно хранить строку по значению, а не по ссылке. Это ссылка на временный, который будет уничтожен к тому времени, когда вы позвоните what(),
    • Как влад & Преториан отметил, что вы можете удалить ошибочный код вместо того, чтобы его исправить, и просто std::runtime_error справиться с этим для вас. TBH, если вы не планируете добавлять некоторые специфичные для файла данные в исключение или не планируете перехватывать исключения файлов отдельно, этот класс вам вообще не нужен.

Edit: с предоставленным выводом valgrind, похоже, что разматывание стека никогда не завершается. Так как моя первоначальная мысль о FileDeleter::operator() вызов и бросание выглядят неправильно, разумным тестом будет: что произойдет, если вы вставите try / catch внутри тела main?


Главные примечания:

  1. никогда не бросать исключение внутри деструктора
  2. никогда не называть что-то еще, что может бросить, от деструктора

потому что, если ваш деструктор вызывается во время обработки исключений / разматывания стека, программа немедленно прекратит работу.

2

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

Других решений пока нет …

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