92436


C++で以下のようにwhileループを回しながら、テキストファイルに上書きをしていきたいです。  
下記のコードだと "text" という文字が "texttexttext" と追記されていってしまいます。上書きをするにはどうしたら良いですか？

[code]

    #define TEMP_FILE_NAME  "\\tmp.txt"
    std::ofstream ofs;
    
    SHGetFolderPath(NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE, NULL, 0, TmpPath); 
    PathAppend(TmpPath, _T(TEMP_FILE_NAME));
    
    remove(TmpPath);
    ofs.open(TmpPath,std::ios::trunc);
    
    while(true)
    {
        ofs << "test";
    }
    
[/code]


CC BY-SA 4.0
2022-11-26T02:00:06.900
92435
2022-12-03T08:52:25.290
2022-11-26T05:47:34.230
3060
36446
question
c++
C++でテキストファイルに上書きしていく方法


C++言語ではコンストラクタ・デストラクタが用意されており、[RAII](https://ja.wikipedia.org/wiki/RAII)が実現されています。

質問のように

>
[code]

>     std::ofstream ofs;
>  
[/code]

と実際の変数利用タイミングと全く異なる位置に変数定義してしまうと、コンストラクタ・デストラクタが期待されるタイミングで動作しません。

[code]

    for (;;) {
        std::ofstream ofs;
        ofs.open(TmpPath,std::ios::trunc);
        ofs << "test";
    }
    
[/code]

と変数利用個所で定義されていれば、ループ毎に`ofs`はコンストラクタ・デストラクタが動作し、毎回ファイルへの書き出し・クローズ処理が行われるようになります。

その上でこのように使われることを想定し、`std::ofstream`コンストラクタは`open()`を自動的に呼ぶ機能も備えられており、

[code]

    for (;;) {
        std::ofstream ofs{ TmpPath, std::ios::trunc };
        ofs << "test";
    }
    
[/code]

とできます。この場合、`std::ofstream ofs{ TmpPath, std::ios::trunc }`
の行でファイルオープンが行われ、ループ末尾の `}` でバッファフラッシュとファイルクローズが行われます。

もし仮に`test`のみを書き込みたいのであれば、変数を付けずに

[code]

    for (;;) {
        std::ofstream{ TmpPath, std::ios::trunc } << "test";
    }
    
[/code]

と書くことができます。この場合、この1行だけで、ファイルオープン・バッファフラッシュ・ファイルクローズが行われますので、ループ内に他の処理があってもファイル操作のタイミングが明確になります。

* * *

>
[code]

>     PathAppend(TmpPath, _T(TEMP_FILE_NAME));
>     remove(TmpPath);
>  
[/code]

C++17から[`<filesystem>`](https://cpprefjp.github.io/reference/filesystem.html)が提供されており、こういったパス操作・ファイル操作が可能になっています。

[code]

    #include <filesystem>
    ...
    auto const path = std::filesystem::path{ TmpPath } / TEMP_FILE_NAME;
    std::filesystem::remove(path);
    for (;;) {
        std::ofstream ofs{ path, std::ios::trunc };
        ofs << "test";
    }
    
[/code]

* * *

akira ejiriさんのこの回答は非常に危険な行為であり、推奨できません。

> ofstreamはファイルディスクリプタをメンバに持っているのですが、publicではなく、継承しないと見えません(でした)。
[code]

>     struct tmpfilebuf : public std::basic_filebuf<std::ofstream::char_type>
> {
>         tmpfilebuf(std::basic_filebuf<std::ofstream::char_type> &&other)
>             : std::basic_filebuf<std::ofstream::char_type>(std::move(other))
>         {
>         }
>         int fd() { return this->_M_file.fd(); }
>     };
>  
[/code]

[識別子](https://ja.cppreference.com/w/cpp/language/identifiers)に

>   * キーワードである識別子は、それ以外の目的に使用することはできません。
>   * いずれかの場所に二重のアンダースコアを持つ識別子は予約されています。
>   * アンダースコアで始まりその直後に大文字が続く識別子は予約されています。
>   * アンダースコアで始まる識別子はグローバル名前空間では予約されています。
>

とあるように `_M_file` は予約語であり、この場合、GCC付属のlibstdc++が独自に使用しているものです。いくつかの実装を調べました。

  * [GCC libstdc++-v3の`fstream`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-12.2.0/libstdc++-v3/include/std/fstream#L84-L123)  
[GCC 3.3.0で登場](https://github.com/gcc-
mirror/gcc/commit/d3a193e36cdf55fbff3bd0f244bc36bc09871ee1#diff-78f7933954d13f1dde6d4132bf108dfc91c2e82b0665ccd94fdffc57ada6f755)して以降20年ほど変更ない

  * [LLVM libc++の`fstream`](https://github.com/llvm/llvm-project/blob/llvmorg-15.0.0/libcxx/include/fstream#L214-L293)  
`FILE* __file_;`はあるが`private`でありpublic継承しても参照できない

  * [Microsoft STLの`fstream`](https://github.com/microsoft/STL/blob/main/stl/inc/fstream#L164-L799)  
`FILE* _Myfile;`はあるが`private`でありpublic継承しても参照できない

となっていて、GCCにのみ通用する特殊な技です。


CC BY-SA 4.0
2022-11-26T03:04:39.000
92436
2022-12-03T08:52:25.290
2022-12-03T08:52:25.290
4236
4236
92435
answer


何をしたくてopenをその場所にしたのか分かりませんが、truncを効かせるためにはループ内に配置する必要があります。また、再オープンするためには、closeが必要です。

なので、単純には以下のような修正になります。

[code]

    #include <windows.h>
    #include <shlobj_core.h>
    #include <shlwapi.h>
    #include <tchar.h>
    #include <fstream>
    
    int main() {
    #define TEMP_FILE_NAME  "\\tmp.txt"
        std::ofstream ofs;
        TCHAR TmpPath[MAX_PATH];
    
        ::SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, TmpPath);
        ::PathAppend(TmpPath, _T(TEMP_FILE_NAME));
    
        _tremove(TmpPath);
    
        while (ofs)
        {
            ofs.open(TmpPath, std::ios::trunc);
            ofs << "test";
            ofs.close();
        }
        return 0;
    }
    
[/code]

※コードは動かないと意味ないので補完してあります(_Tを使っていたのでremoveだけ変更しています。)


CC BY-SA 4.0
2022-11-26T07:17:15.860
92443
2022-11-26T07:17:15.860
54957
92435
answer


既に高評価の回答が承認されていますが、  
単に上書きしたいのであれば`ofs.seekp`でファイル先頭までシークすればよいと思います。

[code]

        ofs.seekp(0,std::ios_base::beg);
        ofs << "test";
    
[/code]

* * *

既に書き込まれているデータをすべて上書きしたいときは、トランケートする必要があります。  
`ftruncate()`でトランケートすればよいのですが、ファイルディスクリプタが必要です。  
ofstreamはファイルディスクリプタをメンバに持っているのですが、publicではなく、継承しないと見えません(でした)。

ofstreamのファイルディスクリプタを使用してftruncate()を呼び出す関数truncate_ofstreamを作って呼び出したところ、既に書き込まれているデータを上書きできました。

[code]

        truncate_ofstream(ofs);
        ofs.seekp(0,std::ios_base::beg);
        ofs << "test";
    
[/code]

truncate_ofstreamのコード

[code]

    #include <fstream>
    #include <iostream>
    #include <sys/types.h>
    #include <unistd.h>
    void truncate_ofstream(std::ofstream &f)
    {
        struct tmpfilebuf : public std::basic_filebuf<std::ofstream::char_type> {
            tmpfilebuf(std::basic_filebuf<std::ofstream::char_type> &&other)
                : std::basic_filebuf<std::ofstream::char_type>(std::move(other))
            {
            }
            int fd() { return this->_M_file.fd(); }
        };
        std::basic_filebuf<std::ofstream::char_type> &rdbuf = *f.rdbuf();
        tmpfilebuf tmp(std::move(rdbuf));
        int fd = tmp.fd();
        ftruncate(fd, 0);
        rdbuf = std::move(*(std::remove_reference<decltype(rdbuf)>::type *)&tmp);
        return;
    }
    
[/code]

Ubuntu 20.04.5 LTSで動作確認しましたが、Windowsでうまくいくかは確認していません。

動作確認にしようした環境です。

[code]

    c++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
    libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f6c1de30000)libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f6c1de10000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6c1dc10000)
    
[/code]

余談ですが、ストリームを渡されているがファイルのパスが分からないことを想定し、ストリームをクローズ/再オープンしない前提で回答しています。  
今回のケースでは問題にならないでしょうが、ストリームをclose()するのは影響が大きいと考えました。


CC BY-SA 4.0
2022-11-26T09:43:56.877
92445
2022-12-03T04:05:07.807
2022-12-03T04:05:07.807
35558
35558
92435
answer