4

I've got this constructor which throws an exception

GenericSocket::GenericSocket(const string& hostname, 
                             const string& servname):
                             _hostname(hostname),
                             _servname(servname)
{  
    initHints();
    int rv;
    if((rv = getaddrinfo(_hostname.c_str(), 
                    _servname.c_str(), 
                    &_hints, 
                    &_servinfo)) != 0) {  
        throw GenericSocketException();
    }  

} 

initHints() does a memset of _hints and sets some variables.

I test it with the google test framework like this:

TEST(CreateObject2, getaddrinfoException)
{
    mgs_addrinfo_return = 1; 
    ASSERT_THROW(new GenericSocket("testhost", "4242"), GenericSocketException);
}

The test fails with a core dump:

[ RUN      ] CreateObject2.getaddrinfoException
socket creation failed
terminate called after throwing an instance of 'common::GenericSocketException'
  what():  Socket creation failed
[1]    43360 abort (core dumped)  ./bin/test_common

Besides the fact that I dont know exactly what goes wrong, I suspect some uninitialised object gets deleted(?), a lot seems to happen under the hood, so I started to wonder if it is good practice to throw an exception in a constructor. Is it maybe better to put this functionality in another function which I can call after the creation of the object, and handle the exception afterwards?

aschepler
  • 70,891
  • 9
  • 107
  • 161
3vlM33pl3
  • 537
  • 5
  • 14

6 Answers6

11

IMHO, throwing an exception in a constructor is the best way to handle a situation like this - do you really want a usable object if there is no socket? it doesn't make sense to me. If it's failing to resolve that address, there's a reason for that and that is worthy of an exception (as long as you handle it correctly!)

In your particular case, you should test the return value and make the exception more useful... (for example, HostNotFound - which I'd guess is the case here)

Nim
  • 33,299
  • 2
  • 62
  • 101
6

Yes it is. You actually have no choice: constructors have no return values.

But take care of exception safety. See http://www.drdobbs.com/184403429 for instance, or google "strong exception guarantee". Indeed, an object whose constructor has thrown will not be destructed (it has never existed) and must be left in a state which doesn't leak resources.

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
5

Of course, the only reasonable thing to do when you can't construct an object is to throw exception, otherwise you would end up with some zombie objects. And answering your other question, no, you can't destroy an object that wasn't created.

Gene Bushuyev
  • 5,512
  • 20
  • 19
  • So the core dump probably happened when an uninitialised object was deleted, must have been servinfo i think – 3vlM33pl3 Jan 05 '11 at 20:58
  • @Cyberroadie -- uninitialized object cannot be deleted, because it never existed in the first place, because constructor thrown exception. – Gene Bushuyev Jan 05 '11 at 22:31
4

Yes, throwing an exception is the most direct way to report that the constructor encountered problems, since they do not return values.

Destructors, on the other hand, should not throw exceptions, since they will be called in the stack unwinding phase of exception handling, and throwing another exception at this point would result in an abort.

JohnMcG
  • 8,709
  • 6
  • 42
  • 49
1

There are already at least two discussions about this on SO. On both throwing exceptions from constructors and two-phase initialization:

1) When is it right for a constructor to throw an exception?

2) Throwing exceptions from constructors

Community
  • 1
  • 1
charstar
  • 1,177
  • 8
  • 13
1

If you learn at construction that you won't be able to create a valid object then throwing an exception is arguably your best option. The user of the class is thus forbidden from carrying on in case of error, unless they can handle it. That's usually the correct behaviour of a program.

function()
{
    // 1. acquire resources.
    // 2. perform action.
    // 3. release resources.
}

If you can't fulfil step one then the other steps are futile. That's what we use RAII for: we acquire all the resources we need first, somewhat analogous to the old C style of programming with respect to stack variables. With RAII if any of the resources fail to acquire -- via an exception in the constructor -- then all previously acquired resources will release automatically and the second step will never be attempted. All done behind the scenes, automatically, but this assumes you throw an exception in the constructor if you can't create the object. It also assumes the destructor will do the clean-up.

struct Connection {
    Connection() {
        if( ! connect() ) throw ConnectionError();
    }
    ~Connection() {  // may not throw under any circumstances
        try { disconnect(); }
        catch( ... ) { }
    }
    void send(const std::string& message);
    // ...
private:
    bool connect();
    // ...
};

void post(const std::string& message)
{
    // step one is easy for the user:
    Connection c;
    // step two is the bulk of the work:
    c.send("HELO");
    // step three is automatically done for the user.
}
wilhelmtell
  • 57,473
  • 20
  • 96
  • 131