How it works...

Here, we defined the Complex class, and we intend to allocate instances of this class statically. To be safe, we need to make sure that neither the constructor nor the destructor of this class can throw exceptions.

However, both the constructor and the destructor invoke operations that may potentially throw exceptions. The constructor performs memory allocation, while the destructor writes logs to standard output.

The constructor allocates memory using the new operator, which throws an std::bad_alloc exception if memory can't be allocated. We use an std::nothrow constant to select a non-throwing implementation of new. Instead of throwing an exception, new will return nullptr if it can't allocate any memory:

ptr = new(std::nothrow) char[size];

We wrap the body of the constructor in the try block to catch all exceptions. The catch block is empty – if the constructor fails, we can't do much:

} catch (...) {
// Do nothing
}

Since we do not allow any exceptions to propagate to the upper level, we mark our constructor as non-throwing using a C++ keyword, that is, noexcept:

Complex(size_t size) noexcept {

However, we need to know whether an object was created properly. For this purpose, we define a method called IsValid. It returns true if the memory was allocated, or false otherwise:

bool IsValid() const { return nullptr != ptr; }

The destructor does the reverse. It deallocates the memory and logs the status of deallocation to the console. As for the constructor, we do not want any exceptions to be propagated to the upper level, so we wrap the destructor body in a try-catch block:

     try {
if (ptr) {
delete[] ptr;
std::cout << "Deallocated memory" << std::endl;
} else {
std::cout << "Memory was not allocated" << std::endl;
}
} catch (...) {
// Do nothing
}

Now, we declare two global objects, small and large. Global objects are allocated statically. The size of the objects is artificially selected in a way that the small object will be allocated properly, but the allocation of the large object should fail:

Complex small(100);
Complex large(SIZE_MAX);

In our main function, we check and print whether the objects are valid or not:

  std::cout << "Small object is " << (small.IsValid()? "valid" : "invalid")
<< std::endl;
std::cout << "Large object is " << (large.IsValid()? "valid" : "invalid")
<< std::endl;

When we run our program, we see the following output:

As we can see, the small object was allocated and deallocated properly. Initialization of the large object failed, but since it was designed to not throw any exceptions, it did not cause the abnormal termination of our application. You can use a similar technique for statically allocated objects to write robust and safe applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.1.136