0

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

ファイル.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;
}

実際に File ctor に例外をスローさせるために、意図的に /root/.bashrc を開くことにしました。私がそこを投げなければ、ヴァルグリンドは完全に幸せです。ここで例外を使用する際に何が欠けていますか? 単純なファイル ハンドルを「正しく」(例外セーフに) 実装するにはどうすればよいですか?

前もって感謝します!

注:私はすでに基本に苦労しているため、読み取り/書き込み操作はまだありません。

編集 #1: これは --leak-check=full を使用した実際の Valgrind 出力です。

==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: デストラクタでスローされる例外を修正しました。

編集 #3: 代わりに std::runtime_error を使用して FileException クラスを削除しました。

編集 #4: デリーターに NULL チェックを追加しました。

4

1 に答える 1

2

次の問題があります。

  1. fclose(NULL)許可されていません...
    • しかし、幸いstd::unique_ptrなことに if のデリータを呼び出さないget() == nullptrので、ここではこれで問題ありません。
  2. FileDeleter:operator()はデストラクタから呼び出されます。つまり、決してスローしてはなりません。あなたの特定のケースでは、 fclose から返されたエラーstd::terminateが呼び出されます
  3. 例外は、参照ではなく値で文字列を保存する必要があります。その参照は、呼び出すまでに破棄される一時的なものですwhat()
    • Vlad と Praetorian が指摘したように、バグのあるコードを修正する代わりに削除して、それをstd::runtime_error処理してもらうことができます。ファイル固有のデータを例外に追加する予定がない限り、またはファイルの例外を個別にキャッチする予定がない限り、このクラスはまったく必要ありません。

編集: valgrind の出力が提供されると、スタックの巻き戻しが完了していないように見えます。呼び出されてスローすることについての私の最初の考えFileDeleter::operator()は間違っているように見えるので、合理的なテストは次のようになります: main の本体内に try/catch を挿入するとどうなりますか?


一般的注意事項:

  1. デストラクタ内で例外をスローしない
  2. デストラクタからスローされる可能性のあるものを決して呼び出さないでください

例外処理/スタックの巻き戻し中にデストラクタが呼び出されると、プログラムは即座に終了するためです。

于 2013-12-13T20:28:59.537 に答える