1

This problem is quite "a classic", but I can't find a good solution to it.

I am using CreateFile to open a file. Sometimes I would want it to be read-only, sometimes write-only and sometimes I would want the same file to support both read and write. Of course I would like to follow RAII principles.

I am trying to accomplish multiple goals and not so sure how:

  1. If a file was opened only for reading, I don't want it to be possible to write to it (which will result in a runtime error/undefined behaviour). In other words, if file1 was opened with read-only permissions I want file1.write_to_file() to not even compile. What that means is that I can't have one large File class with both read and write operations.

  2. I don't want to expose the handle that is returned in CreateFile. The handle absolutely cannot be a public field nor can it be returned from a public getter in the class where it will be stored.

I think it is pretty evident that a basic File class will be necessary. File will call CreateFile and hold the handle returned from it. How should I proceed from there?

  1. If I define ReadFile and WriteFile that inherit from File, how would I define ReadWriteFile? I would like to avoid multiple inheritance and virtual inheritance.

  2. If I try to go for composition (making File a field of ReadFile), how will ReadFile get the handle to the file from File?

  3. A third way is to not define File. Only ReadFile, WriteFile and ReadWriteFile, however that introduces some code duplication because the constructor and destructor of ReadFile and WriteFile would be very similar.

Please do not offer to use fstream or other methods that do not rely on WinAPI.

6
  • 2
    "I would like to avoid multiple inheritance and virtual inheritance." --- why? This seems like a logical use case for multiple inheritance and virtual members. Jan 4 at 21:12
  • 1
    I think the problem I have answering this question is the very tools I would use to solve it are ones you do not want to use: multiple inheritance and virtual method calls. Or is there something else about your problem that makes these tools a poor fit for a solution. Jan 4 at 21:16
  • @GregBurghardt Would you use virtual inheritance in this case? Or "regular" multiple inheritance?
    – EL_9
    Jan 4 at 21:31
  • @GregBurghardt I wanted to see if there is an alternative approach I was missing. With multiple inheritance I would most likely end up with two separate handles (which isn't necessarily bad, but still seems not really clean when it is possible to write and read with the same handle). With virtual inheritance I would "pay the price" of virtual inheritance even if I am mainly using ReadFile and WriteFile and not ReadWriteFile.
    – EL_9
    Jan 4 at 21:34
  • Why would you need two file handles with multiple inheritance? I know adding a "File" class at the top of the hierarchy is potentially the diamond inheritance problem, but a common base class that has the file handle, where the base class provides protected methods to operate on the file handle might be a good approach. Admittedly, I don't do much C++ programming, so I'm probably not the best person to answer this question. Jan 4 at 22:05

3 Answers 3

1

If I define ReadFile and WriteFile that inherit from File, how would I define ReadWriteFile?

Exactly the same: as a direct derivation from File.

And your next question will be probably how to avoid the code duplication. This can be achieved by moving all common code to File as protected methods, and let ReadFile, WriteFile and ReadWriteFile contain only methods which delegate to them, like

// public
void WriteFile::write_to_file()
{
    // defined in base class File
    write_to_file_internal();
}

Now you may wonder what about ReadWriteFile::write_to_file - yes, the method will exactly look the same as WriteFile::write_to_file - but this is only delegational code, no duplicated logic, and the DRY principle is about how not to duplicate any logic.

All the other methods (as well as the constructor, which should be protected for File as well) works the same way: accept the little amount of boilerplate, but the common logic will only exist once, in File.

Up to this point, there is no MI and no virtual inheritance involved. However, you may want instances of ReadWriteFile to be usable in places where some code does something with instances of WriteFile or ReadFile. The canonical solution then is to introduce two common interface classes, with pure virtual methods. So ReadWriteFile and ReadFile will both inherit from IReadFile and File, ReadWriteFile and WriteFile will both inherit from IWriteFile and File.

So yes, this extended solution requires MI, but with only one implementation inheritance (from File), and all other derivations using interface inheritance. This is a common way how MI can be used in a mostly unproblematic fashion.

1

I think it is pretty evident that a basic File class will be necessary. File will call CreateFile and hold the handle returned from it. How should I proceed from there?

Taking some inspiration from fstream, I would add some very basic file operations to File: Just reading and writing single characters, maybe also an array of characters but nothing more fancy.

Then, have the classes ReadFile and WriteFile that use composition to hold a File object, but they can also accept a File object from external using a protected constructor. ReadFile and WriteFile offer a user-friendly interface for respectively reading and writing a file.

The ReadWriteFile would use multiple inheritance (non-virtual) to inherit from both ReadFile and WriteFile and provides both base classes with the same File instance for the composition.

This would accomplish both your stated goal, as well as the unstated goal that you can pass a ReadWriteFile instance to a function that expects a ReadFile or WriteFile reference.

3
  • @DocBrown, I don't inherit from File, so there is no diamond in the inheritance tree.Some care needs to be taken with cleaning up the shared File instance when using ReadWriteFile. Jan 5 at 8:15
  • Sorry, I just overlooked this by scanning your answer too quickly.
    – Doc Brown
    Jan 5 at 9:42
  • This may work, but I think there will be some asymmetry here in the ReadWriteFile implementation, when there are common methods in ReadFile and WriteFile. Lets say one wants to have a public method get_file_name() in all three classes. ReadWriteFile will have to decide to use either ReadFile::get_file_name() or WriteFile::get_file_name(). Of course, both methods would delegate this call finally to the same File instance, which should prevent most issues here, but the asymmetry makes me wonder if this can lead to serious problems.
    – Doc Brown
    Jan 5 at 9:55
0

You say you don't want to use fstream, but have you considered that you can write your own istream/ostream implementations that use whatever you like? e.g. https://stackoverflow.com/questions/13703823/a-custom-ostream

More simply, I think you'd benefit from making a distinction between file and stream. C# does this with its File class. You'd still need three different File classes to do it the way you want, but you only need a read stream and a write stream. There would then be a way of making both a read stream and a write stream given a ReadWriteFile().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.