-1

I know that there have been many similar questions asked as this such as:

However I have an already existing class with specific behaviors that I don't want to break and what I would like to do is to add an additional function to this class.

The current class I'm working on is a basic TextFileReader class and it inherits from a FileHandler class. I'll show the current class hierarchy so you can see what I already have: You can ignore the ExceptionHandler that is used as it is irrelevant to this question but I left it here as this is how my class is defined.


FileHandler.h

#pragma once

#include <string>
#include <sstream>
#include <fstream>

namespace util {

    class FileHandler {
    protected:
        std::fstream    fileStream_;
        std::string     filePath_;
        std::string     filenameWithPath_;

    private:
        bool saveExceptionInLog_;

    public:
        virtual ~FileHandler();

        FileHandler(const FileHandler& c) = delete;
        FileHandler& operator=(const FileHandler& c) = delete;

    protected:
        FileHandler(const std::string& filename, bool saveExceptionInLog);

        void throwError(const std::string& message) const;
        void throwError(const std::ostringstream& streamMessage) const;
    };

} // namespace util

FileHandler.cpp

#include "FileHandler.h"
#include "ExceptionHandler.h"

using namespace util;

FileHandler::FileHandler(const std::string & filename, bool saveExceptionInLog) :
    saveExceptionInLog_(saveExceptionInLog),
    filenameWithPath_(filename) {
    // Extract path info if it exists
    std::string::size_type lastIndex = filename.find_last_of("/\\");
    if (lastIndex != std::string::npos) {
        filePath_ = filename.substr(0, lastIndex);
    }

    if (filename.empty()) {
        throw ExceptionHandler(__FUNCTION__ + std::string(" missing filename", saveExceptionInLog_));

    }
}

FileHandler::~FileHandler() {
    if (fileStream_.is_open()) {
        fileStream_.close();
    }
}

void FileHandler::throwError(const std::string & message) const {
    throw ExceptionHandler("File [" + filenameWithPath_ + "] " + message, saveExceptionInLog_);
}

void FileHandler::throwError(const std::ostringstream & streamMessage) const {
    throwError(streamMessage.str());
}

TextFileReader.h

#pragma once

#include "FileHandler.h"

namespace util {

    class TextFileReader : public FileHandler {
    public:
        explicit TextFileReader(const std::string& filename);
        virtual ~TextFileReader() = default;

        TextFileReader(const TextFileReader& c) = delete;
        TextFileReader& operator=(const TextFileReader& c) = delete;

        std::string readAll() const;    

    };

} // namespace util

TextFileReader.cpp

#include "TextFileReader.h"

using namespace util;

TextFileReader::TextFileReader(const std::string & filename) :
FileHandler( filename, true ) {
    fileStream_.open(filenameWithPath_.c_str(), std::ios_base::in);
    if (!fileStream_.is_open()) {
        throwError(__FUNCTION__ + std::string(" can not open file for reading"));
    }
}

std::string TextFileReader::readAll() const { 
    std::ostringstream stream;
    stream << fileStream_.rdbuf();

    return stream.str();
}

What I would like to do or what I'm trying to do is to add this method to my TextFileReader

std::vector<std::string> readLineByLine() const;

And I have tried several of the methods from the original Q/A's that I listed above at the beginning of this question.

Some of the issues that I have come into is that in the base class I'm storing a fstream object and not an ifstream or ofstream object so trying to use std::getline in a loop is not working. I was trying to read line by line directly from the filestream handle.

On my next attempt I tried to reuse the already existing readAll() and read all of the file's contents into a single buffer and return that back as a single string, then parse that string into a vector of strings. In this approach in my while loop I am still not able to use std::getline because it is using an ostringstream object and not an istringstream object.


The function should work something like this - pseudo code:

std::vector<std::string> TextFileReader::readLineByLine() const {
    // get contents from file either line by line and store
    // each line into a string and push that string into a
    // vector of strings then return that vector after all 
    // lines have been read.

   // or...

   // read all contents from file into single string buffer and parse
   // that string into a vector of strings and return that vector.
}

And this is where I'm having my issue. Any suggestions?

Edit

I had originally tried user's Bapo's approach but I was getting this compiler error:

1>------ Build started: Project: ChemLab, Configuration: Debug Win32 ------
1>TextFileReader.cpp
1>c:\...\textfilereader.cpp(24): error C2665: 'std::getline': none of the 2 overloads could convert all the argument types
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\string(160): note: could be 'std::basic_istream<char,std::char_traits<char>> &std::getline<char,std::char_traits<char>,std::allocator<char>>(std::basic_istream<char,std::char_traits<char>> &,std::basic_string<char,std::char_traits<char>,std::allocator<char>> &)'
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\string(129): note: or       'std::basic_istream<char,std::char_traits<char>> &std::getline<char,std::char_traits<char>,std::allocator<char>>(std::basic_istream<char,std::char_traits<char>> &&,std::basic_string<char,std::char_traits<char>,std::allocator<char>> &)'
1>c:\...\textfilereader.cpp(24): note: while trying to match the argument list '(const std::fstream, std::string)'
1>Done building project "ChemLab.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

However user one commented that if I made fileStream_ mutable the const function won't work. I went ahead and added the mutable modifier to fileStream_ member and now the function compiles.

Francis Cugler
  • 7,462
  • 1
  • 24
  • 44

1 Answers1

2

You could do something as simple as this:

std::vector<std::string> TextFileReader::readLineByLine() {
    std::vector<std::string> return_vector{};
    std::string line;
    while(std::getline(fileStream_, line)) {
        return_vector.push_back(line);
    }
    return return_vector;

}

Though, after this, your entire stream will be read, and will be "empty"

EDIT: forgot to actually return the vector.

JohnkaS
  • 535
  • 3
  • 16
  • I had tried to pass my `fileStream_` to `getline` and it wasn't compiling... oh I forgot to mention that my function definition should be const, I'll edit my question to reflect that. – Francis Cugler Jun 07 '19 at 15:42
  • I had stated that I had tried that approach. I tried exactly what you have shown me but I'm getting a compiler error, I'll append that to my question. – Francis Cugler Jun 07 '19 at 15:46
  • 3
    @FrancisCugler: It can't be `const`. You're manipulating a member. That's not a `const` operation. – Nicol Bolas Jun 07 '19 at 15:46
  • 2
    @FrancisCugler _"oh I forgot to mention that my function declaration should be const,"_ Unless you make `fileSream_` `mutable` you can't do that with a `const` function. Reading from the file implies changing state of the instance. – πάντα ῥεῖ Jun 07 '19 at 15:47
  • Of course, you could make it `mutable`, no issue with that. Though, a compiler error would be really useful in trying to fix your issue @FrancisCugler – JohnkaS Jun 07 '19 at 15:49
  • Would it be better to not have this function declared as `const` or to add the `mutable` modifier to my filestream member? – Francis Cugler Jun 07 '19 at 15:53
  • I would just keep it as non-const, if it really HAS to be const, then mark your member as mutable. – JohnkaS Jun 07 '19 at 15:54
  • Well it's just reading contents from a text file, so I don't think it should have to be mutable and I don't think this function has to be const for that matter either. However the first method to read the entire file from a single buffer being const is okay. – Francis Cugler Jun 07 '19 at 15:55
  • So in truth, my entire issue was coming down to the fact that my function was being declared as `const`... something so simple yet overlooked. – Francis Cugler Jun 07 '19 at 15:56
  • 2
    Do you know what the "mutable" keyword does? It allows you to edit those members when your struct itself is `const`, meaning you can read from a file when you have a `const` method. – JohnkaS Jun 07 '19 at 15:57
  • Yes I know what mutable is. – Francis Cugler Jun 07 '19 at 15:57
  • I just removed the const qualifier from the function declaration / definition and tested it, and it works fine! – Francis Cugler Jun 07 '19 at 16:02
  • @FrancisCugler _"so I don't think it should have to be mutable"_ What makes you actually think so? If you're accessing the `fileStream_` with `std::getline()` this implies changing it's state, as already mentioned. Why you tried to make your function `const` in 1st place, is beyond our perception. – πάντα ῥεῖ Jun 07 '19 at 16:16
  • @πάνταῥεῖ When I originally was trying to use `getline` I wasn't completely aware at the time that it would actually modify the `filestream`. I either learned something new today or was reminded of something that was forgotten and out of mind. And since the original method to get all of the contents does not use `getline` as it uses a `ostringstream` to read the `filestream`'s buffer making that `const` was never an issue. This is older code integrated with a few other classes, an exception handler and a logger... – Francis Cugler Jun 07 '19 at 17:16
  • @πάνταῥεῖ I want to continue to use these classes as they are, but I wanted to add this method to have the ability to read line by line as well. So I just removed the `const` from the function declaration as it doesn't have to be `const`. – Francis Cugler Jun 07 '19 at 17:19