Class member initializer to initialize ifstream with error check? - c++

I am trying to use C++11 feature of class member initializer to initialize variables of class. The variables of class that I have are std::string and std::ifstream.
class A{
std::string filename = "f1.txt";
std::ifstream filestream = ....
public:
....
};
Is there any way to initialize filestream and also check for error at the same time using class member initialization.
What I want to do is, something similar to below :
class A{
std::string filename = "f1.txt";
std::ifstream filestream(filename);
if(filestream.is_open()) .... // check if file cannot be opened
public:
....
};

You can write and call an inline lambda-expression that performs the appropriate checks; such a lambda-expression has access to the data members:
class A {
std::string filename = "f1.txt";
std::ifstream filestream = [&] {
std::ifstream fs{filename};
if (!fs)
throw std::runtime_error("failed to open ifstream");
return fs;
}();
};
It may be clearer to separate out the logic into a reusable helper function taking filename as a parameter, e.g. a static member function:
class A {
std::string filename = "f1.txt";
std::ifstream filestream = openChecked(filename);
static std::ifstream openChecked(std::string const& filename)
{
std::ifstream fs{filename};
if (!fs)
throw std::runtime_error("failed to open ifstream");
return fs;
}
};

Related

How to initialize class member outside the member initializer list

I have a C++ class that takes a string (file path) in its constructor, loads that file into a JSON and then uses some of the JSON variables to initialize another member.
As the file needs to be loaded first, and the the JSON initialized (from stream), I cannot initialize thing in the member initializer list. Should I use another wrapper class for the JSON, use new...? How can I achieve this?
class Dummy
{
std::string _configFilePath;
json configJson;
Thing thing;
Dummy(std::string configFilePath = "../config.json") :
_configFilePath(configFilePath)
{
std::ifstream ifs(configFilePath);
ifs >> configJson;
thing(configJson["whatever"]); // thing can't be initialized here
}
};
Note that thing is not default constructible.
You can use a helper function to do what the current constructor does:
class Dummy
{
std::string _configFilePath;
json configJson;
Thing thing;
Thing loader() {
std::ifstream ifs(_configFilePath);
ifs >> configJson;
return Thing(configJson["whatever"]);
}
Dummy(std::string configFilePath = "../config.json") :
_configFilePath(configFilePath), thing(loader())
{
}
};
This will construct _configFilePath and default construct configJson, then call loader to load the thing. RVO should enable construction of thing directly in loader.
Is Thing both default constructable and move-assignable? If so:
class Dummy
{
std::string _configFilePath;
json configJson;
Thing thing;
Dummy(std::string configFilePath = "../config.json") :
_configFilePath(configFilePath)
{
std::ifstream ifs(configFilePath);
ifs >> configJson;
thing = Thing(configJson["whatever"]); // move-assign a new object
}
};
You can use the combination of a delegating constructor and a helper function that loads the json object to properly initilize the objects.
class Dummy
{
std::string _configFilePath;
json _configJson;
Thing _thing;
// Use a delegating constructor
Dummy(std::string configFilePath = "../config.json") :
Dummy(configFilePath, getConfig(configFilePath) {}
Dummy(std::string configFilePath, json configJson) :
_configFilePath(configFilePath),
_configJson(configJson),
_thing(configJson["whatever"]) {}
// Load and return the json object.
static json getConfig(std::string configFilePath)
{
std::ifstream ifs(configFilePath);
json configJson;
ifs >> configJson;
return configJson;
}
};

How to prevent class object inside a class to try constructing itself automatically without a default constructor?

I have this next class:
#include "OutStream.h"
#include "FileStream.h"
class Logger
{
OutStream _os;
FileStream _fs;
bool _logToScreen;
static unsigned int _line;
public:
Logger(const char *filename, bool logToScreen);
~Logger();
void print(const char *msg);
};
The class OutStream has a default constructor but the class FileStream doesn't have a default constructor.
When I'm trying to code the constructor for Logger i get an error that says "No default constructor exists for class 'FileStream'"
Here is the code of the Logger constructor, where I construct _fs also:
unsigned int Logger::_line = 0;
Logger::Logger(const char* filename, bool logToScreen)
{
// Opening the given file in the FileStream class
_fs = FileStream(filename);
// Declaring the value of _logToScreen
_logToScreen = logToScreen;
}
How can I fix this issue while keeping the _fs private in Logger?
Use the mem-initializer-list. Define the constructor like
Logger::Logger(const char* filename, bool logToScreen) : _fs( filename ), _logToScreen( logToScreen )
{
}

Difficulty overloading operator<< for file handling class

I have to:
Define a File_handle class with constructor that takes a string argument (file name), opens the file in the constructor, and closes it in the destructor.
As I understand it, this class is used to provide RAII and I am trying to implement the class using FILE* as basic data structure where my goal basically is to make FILE* a smart pointer:
fileHandler.h:
// Class CFile_handler based on FILE*
class CFile_handler {
public:
CFile_handler(); // default constructor
CFile_handler(const std::string& fileName, // constructor
const std::string& mode);
~CFile_handler (); // destructor
// modifying member function
void open_file(const std::string& fileName,
const std::string& mode);
protected:
typedef FILE* ptr;
private:
CFile_handler(const CFile_handler&); // prevent copy creation
CFile_handler& operator= (const CFile_handler&); // prevent copy assignment
ptr c_style_stream; // data member
};
fileHandler.cpp:
// Class CFile_handler member implementations
// default constuctor
CFile_handler::CFile_handler() {
}
// constructor
CFile_handler::CFile_handler(const std::string& fileName, const std::string& mode = "r")
: c_style_stream( fopen( fileName.c_str(), mode.c_str() ) )
{
}
// destructor
CFile_handler::~CFile_handler() {
if (c_style_stream) fclose(c_style_stream);
}
// Modifying member functions
void CFile_handler::open_file(const std::string& fileName, const std::string& mode) {
c_style_stream = ( fopen( fileName.c_str(), mode.c_str() ) );
}
However, I'm having difficulties in overloading I/O operators<< / >>, as I can't figure out how to implement either of them.
How to overload operator<< such that the class works with iostream objects?
Edit:
As it was proposed by #LokiAstari, it would be better strategy to inherit from istream and define own streambuf.
Could someone give an example or directions for the implementation of streambuf that handles FILE*?
What I want to provide is:
CFile_handler fh("filename.txt", "r");
std::string file_text;
fh >> file_text;
or:
CFile_handler fh("filename.txt", "w");
fh << "write this to file";
You can derive types of the std::streams using std::streambuf to handle the FILE*
#include <iostream>
#include <stdio.h>
class OutputFilePointerStream: public std::ostream
{
class OutputFilePointerStreamBuf: public std::streambuf
{
FILE* buffer;
public:
OutputFilePointerStreamBuf(std::string const& fileName)
{
buffer = fopen(fileName.c_str(), "w");
}
~OutputFilePointerStreamBuf()
{
fclose(buffer);
}
virtual std::streamsize xsputn(const char* s, std::streamsize n) override
{
static char format[30];
sprintf(format, "%%.%lds", n);
fprintf(buffer, format, s);
return n;
}
};
OutputFilePointerStreamBuf buffer;
public:
OutputFilePointerStream(std::string const& fileName)
: std::ostream(nullptr)
, buffer(fileName)
{
rdbuf(&buffer);
}
};
int main()
{
OutputFilePointerStream fileStream("Test");
fileStream << "Testing: " << 5 << "><\n";
fileStream << "Line Again\n";
}
Your operator<< function is to output a CFile_handler object to a C++ output stream, it's not for outputting to a CFile_handler object.
To output to a CFile_handler object you have two choices:
As a member function
CFile_handler& CFile_handler::operator<<(int value)
{
// Output an integer to the contained file
return *this;
}
Or as a non-member function which takes a CFile_handler reference as first argument:
CFile_handler& operator<<(CFile_handler& file, int value)
{
// Output an integer to the file contained in `file`
return file;
}
For both of the above variants, you can then do e.g.
CFile_handler my_file(...);
my_file << 1234;

Can I get the name of file used from ifstream/ofstream?

I need to know if there exists a method in ifstream so I can get the name of the file tied to it.
For instance
void some_function(ifstream& fin) {
// here I need get name of file
}
Is there a method in ifstream/ofstream that allows to get that?
As mentioned there's no such method provided by std::fstream and it's derivates. Also std::basic_filebuf doesn't provide such feature.
For simplification I'm using std::fstream instead of std::ifstream/std::ofstream in the following code samples
I would recommend, to manage the underlying file name in a little helper class yourself:
class MyFstream {
public:
MyFstream(const std::string& filename)
: filename_(filename), fs_(filename) {
}
std::fstream& fs() { return fs_; }
const std::string& filename() const { return filename_; }
private:
std::string filename_;
std::fstream fs_;
};
void some_function(MyFstream& fin) {
// here I need get name of file
std::string filename = fin.filename();
}
int main() {
MyFstream fs("MyTextFile.txt");
some_function(fs):
}
Another alternative,- if you can't use another class to pass to some_function() as mentioned above -, may be to use an associative map of fstream* pointers and their associated filenames:
class FileMgr {
public:
std::unique_ptr<std::fstream> createFstream(const std::string& filename) {
std::unique_ptr<std::fstream> newStream(new std::fstream(filename));
fstreamToFilenameMap[newStream.get()] = filename;
return newStream;
}
std::string getFilename(std::fstream* fs) const {
FstreamToFilenameMap::const_iterator found =
fstreamToFilenameMap.find(fs);
if(found != fstreamToFilenameMap.end()) {
return (*found).second;
}
return "";
}
private:
typedef std::map<std::fstream*,std::string> FstreamToFilenameMap;
FstreamToFilenameMap fstreamToFilenameMap;
};
FileMgr fileMgr; // Global instance or singleton
void some_function(std::fstream& fin) {
std::string filename = fileMgr.getFilename(&fin);
}
int main() {
std::unique_ptr<std::fstream> fs = fileMgr.createFstream("MyFile.txt");
some_function(*(fs.get()));
}
No. C++ streams do not save the name or the path of the file.
but, since you need some string to initialize the stream anyway, you can just save it for future use.
No, such a method does not exist.

ifstream variable in class

I have my class which has to have ifstream file in it.
I dont know how to present it in the class header
A:
class MyClass
{
...
ifstream file;
...
}
B:
class MyClass
{
...
ifstream& file;
...
}
I know that ifstream has to get path in the decaleration, so how do I do it?
Also how do I open a file with it?
EDIT:
I want the first way, but how do I use it SYNTAX-ly?
let's say this is the header(part of it)
class MyClass
{
string path;
ifstream file;
public:
MyClass();
void read_from_file();
bool is_file_open();
...
}
funcs
void MyClass::read_from_file()
{
//what do I do to open it???
this->file.open(this->path); //Maybe, IDK
... // ?
}
You more than likely want the first option. The second is a reference to some other ifstream object, rather than an ifstream that belongs to MyClass.
You don't need to give an ifstream a path immediately. You can later call the ifstream's open function and give that a path. However, if you want to open the ifstream immediately on initialisation, you need to use the constructor's initialisation list:
MyClass() : file("filename") { }
If you need the constructor to take the file name, simply do:
MyClass(std::string filename) : file(filename) { }
Initialise it in the constructor:
class my_class {
public:
my_class(char const* path) : file(path) {
}
my_class(std::string const& path) : my_class(path.c_str()) {
}
private:
std::ifstream file;
};
Also see The Definitive C++ Book Guide and List.