Be aware that the following:
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
/* ... */
public:
Foo() {} // or Foo() = default or elision
};
int main() {
Foo f;
f.m_bar = "wunderBar";
}
Expands out to be along the lines of the following:
Foo* fptr = stack_allocate<Foo*>(sizeof(Foo));
// from ctor
fptr->m_bar.string("Bar"); // construct m_bar
fptr->m_baz.int(3);
fptr->m_boo.float(4.2);
// your code:
fptr->m_bar.operator=("wunderBar");
For similar reasons, you might want to look at the IL instructions for your C# construct - you'll find it's performing equally redundant operations (and in more complex situations, possibly boxing/unboxing).
Your C++ approach will also fail you when you incorporate non-copyable or non-movable types which will force you to pass pointers and/or bend your design.
What it /seems/ you are trying to do is recreate Python's optional parameters:
# C++-alike
class Foo(object):
def __init__(self, Bar, Baz, Boo):
...
# C#-alike:
class Foo(object):
def __init__(self, Bar="Bar", Baz=13, Boo=4.2):
...
C++ doesn't provide a direct way of doing this, the closest mechanisms are default parameters and operator overloading:
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
public:
Foo(std::string bar="Bar", int baz=3, int boo=6.1)
: m_bar(bar), m_baz(baz), m_boo(boo)
{}
/* Foo* f = new Foo(); => new Foo(bar="Bar", baz=13, boo=6.1);
* Foo* f = new Foo("hello"); => new Foo(bar="hello", baz=3, boo=4.2);
* Foo* f = new Foo("hello", 1, 1.); => new Foo(bar="hello", baz=1, boo=1.);
* Foo* f = new Foo(42.); => invalid, arguments must be in order.
*/
};
or
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
public:
Foo() = default;
// allow Foo("hello")
Foo(const char* bar) : m_bar(bar) {}
Foo(const std::string& bar) : m_bar(bar) {}
Foo(std::string&& bar) : m_bar(std::forward(bar)) {}
// allow Foo(10, 12.)
explicit Foo(int baz, float boo) : m_baz(baz), m_boo(boo) {}
/* Foo* f = new Foo(); => new Foo(bar="Bar", baz=3, boo=4.2);
* Foo* f = new Foo("hello"); => new Foo(bar="hello", baz=3, boo=4.2);
* Foo* f = new Foo(1, 1.); => new Foo(bar="Bar", baz=1, boo=1.);
* Foo* f = new Foo(42.); => invalid, no match
*/
};
See http://ideone.com/yFIqlA for SSCE.
If you genuinely have a dozen different constructor configurations, you should probably rethink your design.
--- Edit ---
Note: It's not compulsory that you expose all parameters in the constructor:
class Foo {
std::string m_user_supplied;
std::time_t m_time;
public:
Foo() : m_user_supplied(), m_time(0) {}
Foo(std::string src) : m_user_supplied(src), m_time(0) {}
void addTime(time_t inc) { m_time += inc; }
};
--- Edit 2 ---
"should maybe rethink your design" ... One problem with large, optional lists of parameters is growth. You are likely to wind up with parameters that depend on each other, contradict each other or interact with each other. You can either choose not to validate these or you can end up with complicated constructors.
struct Foo {
...
FILE* m_output;
const char* m_mode;
...
Foo(..., FILE* output, const char* mode, ...)
{
...
if (output != nullptr) {
ASSERT( output == nullptr || mode != nullptr );
... other requirements
} else {
if (mode != nullptr)
... might not be an error but it might be a bug ...
}
...
}
};
One approach to avoiding this is to use encapsulation/aggregation of related members.
class Foo {
...
struct FileAccess {
FILE* m_file;
const char* m_mode;
constexpr FileAccess() : m_file(nullptr), m_mode(nullptr) noexcept {}
FileAccess(FILE* file, const char* mode) : m_file(file), m_mode(mode) {
if (file == nullptr || mode == nullptr)
throw invalid_argument("file/mode cannot be null");
}
};
...
FileAccess m_access;
Foo(..., FileAccess access, ...);
};
This can go a fair way to reducing the bloat. If your API is stable, you can use it with initializer lists (if your API is not stable and you do change will bite you in the ass)
auto fooWithFile = make_unique<Foo>{..., /*access=*/{stdout, "w"}, ...};
auto fooWithout = make_unique<Foo>{..., /*access=*/{}, ...};
If you subsequently decide to stop using ctors and switch to using setters, this will translate reasonably well, since you can have overloaded "set" which takes one of the various configuration structs:
auto foo = make_unique<Foo>();
foo->set(FileAccess(stdout, "w"))
->set(Position(Right, -100, Top, 180))
->set(DimensionPercentage(80, 75));
vs
auto foo = make_unique<Foo>() { # pseudo based on if C++ had the C# syntax
m_file = stdout;
m_mode = "w";
m_xPosition = -100;
m_xPositionRel = Right;
m_yPosition = -180;
m_yPositionRel = Top;
m_dimensionType = Percentage;
m_xDimension = 80;
m_yDimension = 75;
};