Search code examples
c++c++11iostreamstreambuf

CRTP call child function in destructor of parent


I have two classes structured like so (simplified the code to show the problem more clearly):

template<typename stream_type>
class Stream : public std::basic_streambuf<char, std::char_traits<char>>
{
private:
    std::string pBuffer;

    //other functions overridden here..

public:
    Stream();
    virtual ~Stream();

    Stream(const Stream& other) = delete;
    Stream& operator = (const Stream& other) = delete;
};

template<typename stream_type>
Stream<stream_type>::Stream() : pBuffer()
{
    parent_type::setg(nullptr, nullptr, nullptr);
    parent_type::setp(nullptr, nullptr);
}

template<typename stream_type>
Stream<stream_type>::~Stream()
{
    //Parent Destructor calling child member function..
    static_cast<stream_type*>(this)->sync(&pBuffer[0], pBuffer.size());
}


//CRTP Child..
template<typename char_type>
class File : public Stream<File<char_type>>
{
private:
    FILE* hStream;

public:
    File(const char* path) : Stream<File<char_type>>()
    {
        hStream = fopen(path, "w");
    }
    ~File()
    {
        //Child destructor is closing the file..
        fclose(hStream);
    }

    int sync(const char_type* data, std::size_t size)
    {
        if (fwrite(data, sizeof(char_type), size, hStream) == size)
        {
            fflush(hStream);
        }

        return traits_type::eof();
    }
};

Problem:

When the destructor of the child is called due to going out of scope, it closes the file first.. After that, it calls the parent destructor.. but the parent is still trying to access the child's "sync" function (of course this is an error)..

Any ideas on how I can fix such a situation? I need the parent class to guarantee that all data in its buffer is synced to disk.. However, my child class may NOT always be a "file" class. It might be another type of stream that doesn't sync. I need the parent class to force all the children to sync their data.

Any ideas how I can do that?


Solution

  • Members and bases are destroyed in the reverse order they are created.
    So one solution could be to have a wrapper class, around FILE*, and have
    that as an earlier base than Stream, so that it get destroyed later;

    template<typename char_type>
    class File : private CFileWrapper, public Stream<File<char_type>>