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.
Related
Let's start with a simple compressed file reader class using boost::iostreams:
class SingleFileOpener{
public:
SingleFileOpener(const std::string& filename, bool is_compressed) {
if(is_compressed) m_stream.push(bio::zlib_decompressor());
m_stream.push(bio::file_source{filename});
}
void print() {
bio::copy(m_stream, std::cout);
}
private:
using unseekable_stream = boost::iostreams::filtering_istream;
unseekable_stream m_stream;
};
Now calling SingleFileOpener("input.txt", true) followed by print() works correctly. Coliru Link
I want to extend my class to read and manipulate multiple files in a similar manner. Below is the sample code I tried out(commented out in the Coliru link above too):
class MultiFileOpener{
public:
MultiFileOpener(const std::vector<std::string> filenames, std::vector<bool> is_compressed) {
for(auto i = 0u; i < filenames.size(); i++) {
unseekable_stream s;
if(is_compressed[i]) s.push(bio::zlib_decompressor());
s.push(bio::file_source{filenames[i]});
m_stream.emplace_back(s); // <- error: use of deleted function(copy ctor)
}
}
void print(int i) {
bio::copy(*m_stream[i], std::cout);
}
private:
using unseekable_stream = boost::iostreams::filtering_istream;
std::vector<boost::optional<unseekable_stream>> m_stream;
};
The above doesnt compile due to missing copy constructors in base classes. I've tried using boost::optional, std::shared_ptr and miscellaneous alternatives used for delayed initialization. Uptil now the only solution that has worked is to use an initializer list constructor for the std::vector, i.e. doing ctor: m_stream(filenames.size()) {...}. I had 2 questions:
Why is a copy constructor even being called here?
Is it possible to do this without the initializer list way?
Why is a copy constructor even being called here?
Here:
m_stream.emplace_back(s);
Is it possible to do this without the initializer list way?
Option 1
Use a list:
std::list<unseekable_stream> m_stream;
Change the for loop as follows:
m_stream.emplace_back();
auto& s = m_stream.back();
if(is_compressed[i]) s.push(bio::zlib_decompressor());
s.push(bio::file_source{filenames[i]});
Option 2
Use unique_ptr:
std::vector<std::unique_ptr<unseekable_stream>> m_stream;
For loop code:
auto stream_ptr = std::make_unique<unseekable_stream>();
... //same as above but change . to ->
m_stream.push_back(std::move(stream_ptr));
Option 3
Initialize vector with size and not use push_back or emplace_back.
std::vector<unseekable_stream> m_stream;
MultiFileOpener(const std::vector<std::string>& filenames, const std::vector<bool>& is_compressed)
: m_stream(filenames.size())
{
for(auto i = 0u; i < filenames.size(); i++) {
unseekable_stream& s = m_stream[i];
if(is_compressed[i]) s.push(bio::zlib_decompressor());
s.push(bio::file_source{filenames[i]});
}
}
With this, you cannot add or remove streams later. If those features are needed, use the other options.
I have this code:
class myclass
{
std::string str;
public:
void setStr(std::string value)
{
str=value;
}
std::string getStr()
{
return str;
}
}
main()
{
myclass ms;
std::cout<<ms.getStr()<<std::endl;
}
when I compile and run this code, there is o error and in windows I am always getting str as "".
Is this always valid?
I need the above behavior in the fact that if user did not call set, str would be always a blank string.
should I initialize str in constructor as follow:
class myclass
{
std::string str;
public:
myclass():str(""){}
void setStr(std::string value)
{
str=value;
}
std::string getStr()
{
return str;
}
}
I want to make sure that behavior is the same on all platform and also make sure that code is as small and neat as possible.
Do I need to initialize std::string
No. std::string default constructor initialises a nice empty string for you.
I want to make sure that behavior is the same on all platform and also make sure that code is as small and neat as possible.
Remove clutter then:
struct myclass {
std::string str;
};
Fundamental types though, do not get initialised by default, you need to initialize them explicitly:
struct myclass {
std::string str;
int i = 1; // <--- initialize to 1.
};
You dont need to initialize a string member with an empty string, though it can help to do it anyhows. Consider:
struct foo {
std::string a;
std::string b;
foo() : a("foo") {}
};
Was it by accident or on purpose that b does not get a value in the constructor? I'd prefer
foo() : a("foo"), b() {}
because it makes the intend explicit for no price (not counting a few keystrokes).
I have the following code:
struct A
{
const string name;
A(string name) :name(name) {}
};
struct Parent
{
public:
const decltype(make_tuple(A("AA"))) children{ make_tuple(A("AA")) };
Parent()
{
}
};
Is it possible to avoid typing A("AA") twice?
Like when you use the auto keyword- but working.
You can move A("AA") or even better make_tuple(A("AA")) into its own function:
namespace {
auto make_children() { return make_tuple(A("AA")); }
}
struct Parent
{
public:
const decltype(make_children()) children{ make_children() };
Parent()
{
}
};
Live example
That way you only need to repeat the name of the helper function twice. Depending on the size/complexity of the expression in your real code, that might be a win.
I have an object, every member variable in this object has a name which I can acquire it by calling get_name() ,what I want to do is concatenate all the names of the member variables in alphabetical order, then do something. for example:
class CXMLWrapper<class T>
{
public:
CXMLWrapper(const char* p_name) : m_local_name(p_name)
{
}
//skip the get_name(), set_name() and others
private:
string m_local_name;
T m_type_var;
}
class object
{
public:
object() : m_team("team"), m_base("base")
{
}
public:
CXMLWrapper<string> m_team;
CXMLWrapper<string> m_base;
...
}
I have to hard-code like this:
object o;
string sign = o.m_base.get_name();
sign += o.m_team.get_name();
I need a function to do this instead of copying and pasting when the object varies. Anyone has an idea?
One way to do this in normal C++, provided all of the members belong to the same class or are derived from some base class will be to use variable number of arguments to a function. An example follows.
#include <stdarg.h>
string concatenateNames(int numMembers, ...)
{
string output;
va_list args;
va_start(args, numMembers);
for(int i = 0; i < numMembers; i++)
{
MemberClass *pMember = va_arg(args, MemberClass*);
output += pMember->get_name();
}
va_end(args);
return output;
}
class Object
{
public:
MemberClass x;
MemberClass y;
MemberClass z;
};
int main()
{
Object o;
string sign = concatenateNames(3, &o.x, &o.y, &o.z);
}
If the types of all the members are different, you can look into variadic templates of C++11x: http://en.wikipedia.org/wiki/Variadic_Templates, but I can't seem to find a way to do otherwise.
If variables which have name have a same type (or these types belongs one hierarchy) you can use map of these vars. Is not good way, but maybe it helps you
Example
class object
{
public:
object() //: m_team("team"), m_base("base")
{
this->vars["m_team"] = CXMLWrapper<string>("team");
//.....
}
public:
map<string, CXMLWrapper<string> > vars;
/*CXMLWrapper<string> m_team;
CXMLWrapper<string> m_base;*/
...
}
object o;
string sign;
for(auto& x : o.vars)//i cannot remember syntax of for of map
sign += x.get_name;
PS Sorry for my writing mistakes. English in not my native language.
One method is to have an external library of member names which the CXMLWrapper class updates:-
class BaseXMLWrapper
{
public:
void ListMembers (const char *parent)
{
// find "parent" in m_types
// if found, output members of vector
// else output "type not found"
}
protected:
void RegisterInstance (const char *parent, const char *member)
{
// find 'parent' in m_types
// if not found, create a new vector and add it to m_types
// find 'member' in parent vector
// if not found, add it
}
private:
static std::map <const std::string, std::vector <const std::string> >
m_types;
};
class CXMLWrapper <class T, const char *parent> : BaseXMLWrapper
{
public:
CXMLWrapper(const char* p_name) : m_local_name(p_name)
{
RegisterInstance (parent, p_name);
}
// you could override assignments, copy and move constructors to not call RegisterInstance
//skip the get_name() set_name()
private:
m_local_name;
}
class object
{
public:
object() : m_team("team"), m_base("base")
{
}
public:
CXMLWrapper<string, "object"> m_team;
CXMLWrapper<string, "object"> m_base;
...
};
This does add overhead to the construction of objects, but as it's only a constructor overhead it might not affect overall system performance much.
This looks like a "observe pattern", you just need to keep a single copy in object as a member variable "string name_;", and pass the name_s's reference into CXMLWrapper like this:
class CXMLWrapper<class T>
{
public:
CXMLWrapper(const string &name)
: local_name_(name)
{
}
//skip the get_name() set_name()
private:
const string &local_name_;
}
class object
{
public:
object()
: team_("team"),
base_("base"),
m_team(team_)
, m_base(base_)
{
}
public:
string team_;
string base_;
CXMLWrapper<string> m_team;
CXMLWrapper<string> m_base;
}
this may be an amateur question, but here it goes. I have three clases:
DrawableObject:
class DrawableObject
{
private:
int x;
//...
public:
int getX();
//...
}
FormElement which inheirts from DrawableObject:
class FormElement : public DrawableObject
FormElement has a method called wasPushed:
bool FormElement::wasPushed(SDL_Event event)
{
bool wasPushed =
(
( event.motion.x >= getX()) //Inherited getX()
&& // Blah blah...
) ? true : false;
return wasPushed;
}
Finally, TextField, which inheirts from FormElement:
class TextField : public DrawableObject
I also have a class, named Form:
class Form {
public:
list<FormElement*> getFormElements();
void generateForm();
private:
list<FormElement*> formElements;
}
Form adds some TextFields to its list, in its generateForm() method:
void Form::generateForm() {
TextField *aTextField = new TextField(10, 10, 120);
this->formElements.push_back(aTextField);
}
Later, it tries to iterate it:
for(list<FormElement*>::iterator it = getFormElements().begin()
; it != getFormElements().end()
; ++it)
{
if ( (*it)->wasPushed(theEvent) )
{ //Etc.
Well, the program exits, when it tries to access getX() from wasPushed method.
Could anyone please tell me why? What am I defining wrong?
I thank you very much.
MartÃn.
You are returning the list by value:
list<FormElement*> getFormElements();
it should be by reference:
list<FormElement*> &getFormElements();
When you return by value, you are getting a temporary copy of the list.
So in this code:
for(list<FormElement*>::iterator it = getFormElements().begin()
; it != getFormElements().end()
Your begin and end iterators are pointing to two different copies of the list. Also, those temporary copies will be destroyed before you ever have a chance to iterate over them.
You could also use the formElements member directly:
for(list<FormElement*>::iterator it = formElements.begin()
; it != formElements.end()
; ++it)