I'm using the code under "Constructors and destructors" here
Basically, i would like to combine it with the pointer construction under "Pointers to classes" (a little below) which i find really neat.
Now this works:
int main () {
CRectangle * prect; //l2
CRectangle rect (3,4); //l3
prect = ▭ //l4
cout << "rect area: " << (*prect).area() << endl;
return 0;
}
My questions is, can we replace l2-l4 by a more elegant method not requiring the creation of rect on line 3?
To create an object without requiring any automatic variables (such as rect in your code above) you must use the new operator. Objects created with the new operator are stored in the free store, and the new expression evaluates into a pointer to the newly created object.
Now, one could go on and say, new operator is the answer and that's it, but it's actually not: it does not answer the question of replacing those couple of lines with a more elegant solution, because it wouldn't be one.
The rest of this answer goes on a tangent of how new could be used.
Unlike automatic objects, objects stored in the free store are not automatically destructed, but rather their life-time and destruction is controlled by the delete operator (you should delete objects you no longer need to free resources). To ensure destruction, you should always store the pointer from a new expression into what's called a smart pointer. A good, simple rule that carries one far: use the new operator only inside a smart pointer constructor (unless you know what you're doing).
There are several smart pointers in C++11, while earlier versions of the standard only defined one, the auto_ptr. Perhaps due to it's quirks, or simply because it got a replacement, it's actually deprecated in C++11, and shouldn't probably be used in new code, at least not in C++11 (now, this is an opinion).
An example of smart pointer use:
boost::shared_ptr<CRectangle> shared_rect(new CRectange(3, 4));
std::unique_ptr<CRectangle> rect(new CRectangle(3, 4)); // C++11 only
// use smart pointers like like regular pointers; indirection through * or ->
// i.e. (*rect).area() or rect->area()
I like @eq-'s answer as well, but in addition to dynamic memory allocation, you can also try placement new. (When I say "try", I don't mean this is the way to do things, but more like this is more food for thought. I am not trying to lead you to a maze of twisty little passages, all alike.)
char mem[sizeof(CRectangle)] alignas(CRectangle);
CRectangle *prect = new (mem) CRectangle(3, 4); // construct
std::cout
<< "rect area: " << prect->area()
<< std::endl;
prect->~CRectangle(); // destruct
You would typically use placement new when working with a custom allocator. A custom allocator could be used for various reasons, but in my work it is typically for pre-allocation strategies (like a pool allocator, or an embedded system with strict memory provisioning). In this case, the placement new is being used to initialize memory that has automatic storage class. Like your original rect object, the memory for the object becomes invalid when the code falls out of scope. However, the CRectangle constructor may perform initializations that require clean up, so there is an explicit call to the destructor before the memory falls out of scope.
Note, you cannot use auto_ptr for prect, because calling delete on the pointer would be undefined (since the memory was not dynamically created). You could use shared_ptr if you pass in a custom deleter to the constructor. You could use unique_ptr if you pass in a custom deleter in the second template parameter. The custom deleter simply makes an explicit call to the destructor so that the smart pointers do the work of cleaning up automatically when the code falls out of scope.
template <typename T>
struct placement_delete {
void operator () (T *t) const { t->~T(); }
};
char mem2[2][sizeof(CRectangle) alignas(CRectangle);
std::shared_ptr<CRectangle>
sprect(new (mem2[0]) CRectangle(2, 3), placement_delete<CRectangle>());
std::unique_ptr< CRectangle, placement_delete<CRectangle> >
uprect(new (mem2[1]) CRectangle(3, 4));
The new call may throw a std::bad_alloc exception. Since it is uncaught, your program will terminate immediately after the exception is thrown with an error result. If you want to gracefully deal with exceptions, you use try and catch blocks.
CRectangle *prect;
try {
prect = new (mem) CRectangle(3, 4);
} catch (std::bad_alloc) {
// do something about it?
abort();
}
For learning, abort is okay. It gets you practicing catching exceptions, but lets you quickly pinpoint the location of the problem when analyzing the error state left behind as the result of the abort. (On UNIX, this is typically a core file.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With