简体   繁体   中英

How to prevent the constructor from creating an object when an exception is thrown

When a constructor throws an exception, how can I prevent the object from being created?

In the example below, I create a Month() class, for which legal values of the int month_ property are in the range of 1 to 12. I instantiate December, or dec , with integer value 13. The exception is thrown, as it should be, but the object is still created . The destructor is then called.

How do I abort creation of a class instance upon a thrown exception?

OUTPUT

-- Month() constructor called for value: 2
-- Month() constructor called for value: 6
-- Month() constructor called for value: 13
EXCEPTION: Month out of range
2
6
13
-- ~Month() destructor called.
-- ~Month() destructor called.
-- ~Month() destructor called.
Press any key to exit

Minimal, complete, and verifiable example

#include <iostream>
#include <string>

class Month {

    public:
        Month(int month) {
            std::cout << "-- Month() constructor called for value: " << month << std::endl;
            try {
                // if ((month < 0) || month > 12) throw 100; Good eye, Nat!
                if ((month < 1) || month > 12) throw 100;
            } catch(int e) {
                if (e == 100) std::cout << "EXCEPTION: Month out of range" << std::endl;
            }
            month_ = month;
        }

        ~Month() {
            std::cout << "-- ~Month() destructor called." << std::endl;
        }

        int getMonth()const { return month_; }

    private:
        int month_;
};

int makeMonths() {
    Month feb(2), jun(6), dec(13);
    std::cout << feb.getMonth() << std::endl;
    std::cout << jun.getMonth() << std::endl;
    std::cout << dec.getMonth() << std::endl;
    return 0;
}

int main() {
    makeMonths();
    std::cout << "Press any key to exit"; std::cin.get();
    return 0;
}

How do I abort creation of a class instance upon a thrown exception?

Well, you throw an exception in the constructor. But there's a catch: Don't catch it !

If you catch it, it would be like the exception never happened. You caught it, so it doesn't go up the call stack anymore. So the object is created, because as far as anyone is concerned, the constructor did not throw an exception.

If you remove the catch clause from the constructor, you'll probably get something like:

-- Month() constructor called for value: 2
-- Month() constructor called for value: 6
-- Month() constructor called for value: 13
terminate called after throwing an instance of 'int'
[1]    28844 abort (core dumped)  ./main

Here, the constructor threw an exception, and it went up the call stack outside of the constructor this time because no one caught it in the constructor. It then goes to makeMonths , where it is also not caught, and then to main , where it is also not caught, and so the program terminates abnormally.

By default, throwing an exception in a constructor should prevent the destructor from ever being called. However, you are catching the exception and handling it.

You could throw a new exception inside your catch that can then be seen outside this scope so that the destructor is not called.

How do I abort creation of a class instance upon a thrown exception?

You just (re-)throw the exception, instead of catching it, I'd recommend to throw a std::invalid_argument or std::out_of_range exception:

class Month {
public:
    Month(int month) {
        std::cout << "-- Month() constructor called for value: " << month << std::endl;
        if ((month < 0) || month > 12)
            throw std::invalid_argument("month");
            // or throw std::out_of_range("month");
        else
           month_ = month;
    }
    ~Month() {
        std::cout << "-- ~Month() destructor called." << std::endl;
    }
    int getMonth()const { return month_; }
private:
    int month_;
};

Your created instance of Month will be properly discarded with the stack unwinding mechanism . There will be never an instance created, thus the destructor isn't called at all.

See a Live Example .


To make the exception more informative, you could use something along these lines:

        if ((month < 0) || month > 12) {
            throw std::out_of_range("'month' parameter must be in the range of 1-12.");
        }

Also if you dislike to initialize the member variable in the constructor's body (as I do mostly)

You may just introduce a lambda expression for the validity checking code:

auto month_check = [](int month) {
    if ((month < 0) || month > 12) {
        throw std::out_of_range("'month' parameter must be in the range of 1-12.");
    }
    return month;
};

class Month {
public:
    Month(int month) : month_(month_check(month)) {
        std::cout << "-- Month() constructor called for value: " << month << std::endl;
    }
    ~Month() {
        std::cout << "-- ~Month() destructor called." << std::endl;
    }

    int getMonth()const { return month_; }
private:
    int month_;
};

Live Demo

You must throw exception for example to main, Message about constructor schould be in block try . So... 2. objects are created and when 3. object being created, exception throw 100 and is handled in main. It's one from a lot of possibilities, I think.

#include <iostream>
#include <exception>
#include <iostream>
#include <string>

class Month {
public:
    Month(int month) {
        try {
            if ((month < 0) || month > 12) throw 100;
            std::cout << "-- Month() constructor called for value: " << month << std::endl;
    }
    ~Month() {
        std::cout << "-- ~Month() destructor called." << std::endl;
    }
    int getMonth()const { return month_; }
private:
    int month_;
};

int makeMonths() {
    Month feb(2), jun(6), dec(13);
    std::cout << feb.getMonth() << std::endl;
    std::cout << jun.getMonth() << std::endl;
    std::cout << dec.getMonth() << std::endl;
    return 0;
}

int main() {
    try {
        makeMonths();
        std::cout << "Press any key to exit"; std::cin.get();
    }
    catch (...)
    {
        std::cout << "exception" << std::endl;
    }

    return 0;
}

OUTPUT:

-- Month() constructor called for value: 2
-- Month() constructor called for value: 6
-- ~Month() destructor called.
-- ~Month() destructor called.
exception

You should throw exception from the constructor and catch it in the code other than constructor eg where object creation is tried.

If a constructor finishes by throwing an exception, the memory associated with the object itself is cleaned up — there is no memory leak

From iso/cpp source 1

If a constructor throws an exception, the object's destructor is not run.

From iso/cpp source 2

You could use the factory method pattern to avoid calling the constructor if the input's not valid.

Gist:

  1. Create a static method called .New() or something.

  2. External entities call .New() instead of the constructor.

  3. .New() validates the input.

    • If the input's good, then it calls the constructor and returns the result.

    • Otherwise, it throws an exception. Or, you can return null , or return a non- null default value.


#include <iostream>
#include <string>

class Month
{
    public:
        static Month New(int month)
        {
            std::cout << "-- Month.New() factory method called for value: " << month << std::endl;

            if (month < 0 || month >= 12)
            {
                std::cout << "-- Month.New() factory method found that month was invalid; throwing exception" << month << std::endl;

                throw /*exception type here*/;
            }

            return Month(month);
        }

        ~Month()
        {
            std::cout << "-- ~Month() destructor called." << std::endl;
        }

        int getMonth()const { return month_; }

    private:
        int month_;

        Month(int month)
        {
            month_ = month;
        }
};

int makeMonths() {
    Month feb(2), jun(6), dec(13);
    std::cout << feb.getMonth() << std::endl;
    std::cout << jun.getMonth() << std::endl;
    std::cout << dec.getMonth() << std::endl;
    return 0;
}

int main() {
    makeMonths();
    std::cout << "Press any key to exit"; std::cin.get();
    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM