Reputation: 1085
I'm new to C++, and am used to working with Java. In Java, I have the option of declaring an object without instantiating it, and would like to do the same in C++.
Assuming there is some class Foo
, in Java I could write Foo bar;
to declare an instance of Foo
without initializing bar
.
However, in C++ when I write Foo bar;
, bar
is initialized by calling the default constructor of class Foo
.
This is particularly vexing if I have written one or more constructors for class Foo
, each of which have at least one argument. In this case, the code will fail to compile with an error similar to no matching function for call to 'Foo::Foo()'
For example, say I have the following class definition in Java:
public class Foo {
private boolean b;
Foo(boolean b) { this.b = b; }
}
and the corresponding class definition in C++:
class Foo {
bool b_;
public:
Foo(bool b) : b_(b) {}
};
In Java, I could write some method
public static Foo makeFoo(int x) {
Foo result;
if (x > 10) { result = new Foo(true); }
else { result = new Foo(false); }
return result;
}
However, if I write a similar method in C++, I get a compilation error:
Foo makeFoo(int x) {
Foo result; // here, a call is made to Foo::Foo() which doesn't exist, and thus compilation fails
if (x > 10) {
result = Foo(true); // this would probably also fail since I didn't specify a copy-constructor, but I'm not entirely sure
}
else {
result = Foo(false); // as above, I think this would probably fail
}
return result;
}
While the example I gave is useless, I frequently used this sort of approach when writing Java code. Is there a way to emulate this behavior in C++? Alternatively, is this just bad design? If so, what sort of approach would you recommend?
Upvotes: 7
Views: 9920
Reputation: 11730
If you don't want to use pointers to get reference functionality as Igor (and others) explained in the first comment on your question, then you can do a couple of things.
First, the philosophy of value types instead of reference types is do not create them until you need them. Your temptation to declare the reference ahead of using the object to obtain a sort of polymorphic functionality in the remainder of the function (some post-create common init code, perhaps) is reasonable design, but cannot be expressed the way you wrote it because it would involve creating a value.
You could provide a default constructor and give it some behaviour -- but it's pretty clear that neither you nor anybody else want to be coerced into doing that.
The essence of the alternative is to move the variable inside the scope and return it.
Foo makeFoo(int x) {
if (x > 10) {
Foo result = Foo(true);
return result;
}
else {
Foo result = Foo(false);
return result;
}
}
Obviously this prevents you from writing common post-create init code after the if block before the return. To do that, you would write the if block in its own function and have it return the result, then write the follow up code after you've initialized your object.
Foo premakeFoo(int x) {
if (x > 10) {
Foo result = Foo(true);
return result;
}
else {
Foo result = Foo(false);
return result;
}
}
Foo makeFoo(int x) {
Foo result = premakeFoo(x);
// common post init code can go here.
return result;
}
If you don't want it in a completely separate function, you could do a lambda.
Foo makeFoo(int x) {
Foo result = ([=]() {
if (x > 10) {
Foo result = Foo(true);
return result;
} else {
Foo result = Foo(false);
return result;
}
})();
// common post init code can go here.
return result;
}
Or in your case, if it's small, use a ternary expression inline.
Foo makeFoo(int x) {
Foo result = (x > 10) ? Foo(true) : Foo(false); // or just Foo(x>10)
// common post init code can go here.
return result;
}
There are other clever options involving templates, but they all revolve around the idea of isolating the different expressions for the overloaded constructors and then using an assignment to initialize a variable given some more sophisticated expression. And what you are trying to do is to get the expression that constructs the value in two different ways to span a scope block (span the if blocks). Ternaries and functions are the options here for getting the if logic executed within a single expression.
Upvotes: 8
Reputation: 180500
In Java when you do Foo result;
you create a reference to Foo, you don't actually create an object. C++ is different though since it has value semantics and Foo result;
actually creates an object. If there is no default constructor, then an error is raised.
To get the same type of behavior you need to use pointers in C++. You can't use a reference since a reference needs to be bound to an object when it is created, unlike Java. So, if you use a std::unique_ptr<Foo>
you can declare that, and then initialize it later with a std::unique_ptr<Foo>
that you initialize in your creation logic. Using your example, the code would look like
Foo makeFoo(int x) {
std::unique_ptr<Foo> result; // here result doesn't have an actual object yet
if (x > 10) {
result = std::make_unique<Foo>(true); // now we pass the values we want to construct with and get a valid object
}
else {
result = std::make_unique<Foo>(false); // same as above
}
return *result; // this lets you return a `Foo` which will be a copy, and the Foo created by make_unique will be destroyed
}
If you don't want to make a copy, you can use return result;
instead, and change the function to return a std::unique_ptr<Foo>
.
Upvotes: 3
Reputation: 134
what you are trying to do is called a factory pattern and is usually implemented in this way
std::unique_ptr<Foo> makeFoo(int x) {
std::unique_ptr<Foo> result = nullptr;
if (x > 10) {
result = std::make_unique<Foo>(true);
}
else {
result = std::make_unique<Foo>(false);
}
return result;
}
for the above to work you also need to include the header. refer to this for further information on unique_ptr and memory management in c++
also, note I would not recommend this level of verbosity in code, I wrote that out for presentation purposes. The following version offers fewer lines of code and is more elegant.
std::unique_ptr<Foo> makeFoo(int x) {
if (x > 10) {
return std::make_unique<Foo>(true);
}
return std::make_unique<Foo>(false);
}
Upvotes: 1