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;
}
};
Related
Let's say I have a class Bitmap that has a static cache map textures that holds pointers to all images that have been registered.
class Bitmap {
public:
Bitmap(const std::string &filename);
// ... functionality ...
private:
// ... image data ...
std::string filename;
static std::map<std::string, std::unique_ptr<Bitmap>> images;
}
Is it possible for the constructor of Bitmap to search the cache for an existing object with the same filename and then return a reference to that?
I've tried something like
if (images.find(filename) != images.end()) {
*this = images[filename].get();
return;
}
but that doesn't seem to work. Is there a way at all to achieve this effect using the constructor?
By the time you're actually constructing an object, you're already outside the scope of controlling the object's allocation. The constructor is simply there to initialize the object.
One way to achieve this with minimal changes to your code is to make the constructor private and create a static method to perform the create/cache logic.
class Bitmap {
public:
static Bitmap& Get(const std::string &filename);
private:
Bitmap(const std::string &filename)
: filename(filename)
{
std::cout << "Construct " << filename << "\n";
}
std::string filename;
static std::map<std::string, std::unique_ptr<Bitmap>> images;
};
std::map<std::string, std::unique_ptr<Bitmap>> Bitmap::images;
Bitmap& Bitmap::Get(const std::string &filename)
{
auto it = images.find(filename);
if (it == images.end())
{
std::unique_ptr<Bitmap> ptr(new Bitmap(filename));
it = images.insert(std::make_pair(filename, std::move(ptr))).first;
}
return *(it->second);
}
Driver code:
int main()
{
auto a = Bitmap::Get("foo");
auto b = Bitmap::Get("bar");
auto c = Bitmap::Get("foo");
}
Output:
Construct foo
Construct bar
This essentially turns your Bitmap class into a self-managing cache. If you want to allow both cached and uncached bitmap instances, then you would move the caching stuff to a separate factory class such as BitmapManager.
The sane way to make it work is to move all data members to a struct, and store a std::shared_ptr to it in the Bitmap (and in the static map).
I have the following class:
class MyClass {
public:
MyClass(string something);
~MyClass();
private:
some_namespace::some_class_name my_variable_name;
};
with the following constructor definition:
MyClass::MyClass(string path) {
string other = "jwrhkfg";
my_variable_name = some_namespace::some_class_name(path+other)
}
I'm trying to initialize my_variable_name to something dependent on path. I know I could use initializer list:
MyClass::MyClass(string path) : my_variable_name(path+other) {}
But what if I need to do a lot of processing on top of the path string and then initialize my_variable_name? What if I need to do something like this:
MyClass::MyClass(string path) {
string other = "jwrhkfg";
if (path="site.com") {
other = "oritrot";
} else {
other = "hsghkr";
}
my_variable_name = some_namespace::some_class_name(path+other)
}
I cannot put these ifs in an initializer list.
I thought the code above would work because I only declared the name my_variable_name but didn't define it, so I could do in the constructor, but when I do:
MyClass my_object = MyClass("some_string");
I get that there's no constructor for some_class_name (because the default constructor takes one argument, and here it is trying to initialize even if I didn't initialize it, only declared.
I cannot put these ifs in an initialization list.
You can use a function to determine the value and use that in the initialization list:
string MyClass::getOther(string path) {
if (path == "site.com") {
return "oritrot";
}
return "hsghkr";
}
MyClass::MyClass(string path) : my_variable_name(path + getOther(path)) {
}
Also a ternary conditional would work for your simple case:
MyClass::MyClass(string path)
: my_variable_name(path + (path == "site.com")?"oritrot":"hsghkr") {
}
Make your member a smart pointer instead:
class MyClass {
public:
MyClass(string something);
~MyClass();
private:
std::unique_ptr<some_namespace::some_class_name> my_variable_name;
};
MyClass:MyClass(string path) {
string other = "jwrhkfg";
my_variable_name = std::make_unique<some_namespace::some_class_name>(path+other);
}
Also, if you don't want to create a separate function to be used in intitialization list only, you can use lambdas:
MyClass::MyClass(std::string path)
: my_variable_name(
[&]() -> some_namespace::some_class_name
{
// do work here and return desired value
string other = "jwrhkfg";
if (path="site.com") {
other = "oritrot";
} else {
other = "hsghkr";
}
return some_namespace::some_class_name(path+other);
}() /*call lambda immediately*/ )
{}
Although it's true, that you can use ternary expression is this case (as pointed by user0042), approach with lambda is more generic and can be applicable in case of any complex initialization.
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;
}
};
I have written a class to perform output of data stored in unordered_map according to the pattern. I expect this pattern to be passed to the object at the time of it creation and stay not changed during object lifetime.
This is the class I wrote first:
class Format {
public:
Format(const string output_pattern);
string interpolate(const boost::unordered_map<string, string> & field_values);
private:
...
};
I wanted to use Formatter in another class this way:
class A {
private:
Format formatter;
public:
A(const std::map<std::string, std::string> & info, ... other parameters) {
std::map<std::string, std::string>::const_iterator it;
it = info.find("format");
if (it == info.end()) {
throw std::runtime_error("Can't find 'format' in info");
} else {
formatter = Format(it->second);
}
}
};
As you see, there is a bit of work before I can call constructor. No surprise, it doesn't work as format is first initialized with default constructor (which is missing), and second it expects assignment operator defined for formatter = Format(it->second); line.
I can't initialize formatter like this:
A(const std::map<std::string, std::string> & info, ... other parameters):
formatter(...) {
...
}
because I have first to extract parameter to pass as formatter initializer.
So, eventually I solved it this way:
class Format {
public:
Format();
void set_pattern(const string output_pattern);
string interpolate(const boost::unordered_map<string, string> & field_values);
private:
...
};
class A {
private:
Format formatter;
public:
A(const std::map<std::string, std::string> & info, ... other parameters):
formatter()
{
std::map<std::string, std::string>::const_iterator it;
it = info.find("format");
if (it == info.end()) {
throw std::runtime_error("Can't find 'format' in info");
} else {
formatter.set_pattern(it->second);
}
}
};
What I really don't like about this is that this turned immutable formatter object into mutable. Here it is not a big issue. But I don't need it to be mutable and don't like it when I have to make it mutable just because I can't express it in code when it is immutable.
Is there any another good approach I can take to initialize it in constructor in my case?
You could do a helper private function to do the actual work:
class A {
private:
SomethingA before;
Format formatter;
SomethingB after;
public:
A(const std::map<std::string, std::string>& info) :
formatter(extractFormat(info))
{
// ...
}
private:
string extractFormat(const std::map<std::string, std::string>& info)
{
// Be careful here! The object is not constructed yet, you can't fully use it
// this->before => ok
// this->after => not ok
// you could make this function static just to be safe
}
};
I have trouble implementing a simple file parser in C++11 which reads a file line by line and tokenizes the line. It should properly manage its resources. Usage of the parser should be like:
Parser parser;
parser.open("/path/to/file");
std::pair<int> header = parser.getHeader();
while (parser.hasNext()) {
std::vector<int> tokens = parser.getNext();
}
parser.close();
So the Parser class needs one member std::ifstream file (or std::ifstream* file?)
1) How should the constructor initialize this->file?
2) How should the open method set this->file to the input file?
3) How should the next line from the file get loaded into a string?
(Is this what you would use: std::getline(this->file, line)) ?
Can you give some advice? Ideally, could you sketch out the class as a code example.
Since the Parser is probably in a pretty useless state once you've constructed it and before you've opened the file, I would suggest having your use case look something like this:
Parser parser("/path/to/file");
std::pair<int> header = parser.getHeader();
while (parser.hasNext()) {
std::vector<int> tokens = parser.getNext();
}
parser.close();
In which case, you should use the constructor's member initialization list to initialise the file member (which, yes, should be of type std::ifstream):
Parser::Parser(std::string file_name)
: file(file_name)
{
// ...
}
If you kept the constructor and open member function separate, you could just leave the constructor as default because the file member will be default constructed giving you a file stream that is not associated with any file. You would then get Parser::open to forward the file name to std::ifstream::open, like so:
void Parser::open(std::string file_name)
{
file.open(file_name);
}
Then, yes, to read lines from the file, you want to use something similar to this:
std::string line;
while (std::getline(file, line)) {
// Do something with line
}
Good job for not falling into the trap of doing while (!file.eof()).
It can be designed in many ways.
You may ask the user to provide you a stream instead of specifying a filename.
That will be more generic and will work in all streams.
That way you should have a std::ifstream& member variable though you can have a pointer type as well but you need to do *_stream << to invoke any operator.
If you take a file, you mat construct a stream in your constructor and close it if open in destructor
Actually, there is an alternative to feeding the name of the file to Parser: you could feed it a std::istream. What's interesting in this is that this way any derived class of std::istream can be used, and thus you could feed it, for example, a std::istringstream, which makes it easier to write unit-tests.
class Parser {
public:
explicit Parser(std::istream& is);
/**/
private:
std::istream& _stream;
/**/
};
Next, comes iteration. It is not idiomatic in C++ to have a has followed by a get. std::istream supports iteration (with an input iterator), you could perfectly design your parser so it does too. This way you will have the benefit of compatibility with many STL algorithms.
class ParserIterator:
public std::iterator< std::input_iterator_tag, std::vector<int> >
{
public:
ParserIterator(): _stream(nullptr) {} // end
ParserIterator(std::istream& is): _stream(&is) { this->advance(); }
// Accessors
std::vector<int> const& operator*() const { return _vec; }
std::vector<int> const* operator->() const { return &_vec; }
bool equals(ParserIterator const& other) const {
if (_stream != other._stream) { return false; }
if (_stream == nullptr) { return true; }
return false;
}
// Modifiers
ParserIterator& operator++() { this->advance(); return *this; }
ParserIterator operator++(int) {
ParserIterator tmp(*this);
this->advance();
return tmp;
}
private:
void advance() {
assert(_stream && "cannot advance an end iterator");
_vec.clear();
std::string buffer;
if (not getline(*_stream, buffer)) {
_stream = 0; // end of story
}
// parse here
}
std::istream* _stream;
std::vector<int> _vec;
}; // class ParserIterator
inline bool operator==(ParserIterator const& left, ParserIterator const& right) {
return left.equals(right);
}
inline bool operator!= (parserIterator const& left, ParserIterator const& right) {
return not left.equals(right);
}
And with that we can augment our parser:
ParserIterator Parser::begin() const {
return ParserIterator(_stream);
}
ParserIterator Parser::end() const {
return ParserIterator();
}
I'll leave the getHeader method and the actual parsing content to you ;)