How to return error code from constructor?
Asked Answered
S

5

9

I am trying to return error code from constructor, since constructor does not return an error code, I tried to put an exception on the constructor. Then in the catch block I return my appropriate error code. Is this a proper way of returning error code from constructor ?

#include <exception>
#include <iostream>

class A {
 public:
  A() { throw std::runtime_error("failed to construct"); }
};

int main() {
  try {
    A a;
  } catch (const std::exception& e) {
    std::cout << "returining error 1 \n";
    return 1;
  }

  return 0;
}
Slowpoke answered 11/8, 2017 at 21:13 Comment(11)
To me, it is a good practice to make the constructor noexcept... Maybe, a good way could to put a state to the object like : invalid. I am looking for a better answer as wellLobel
Just use exceptions throughout. Error return values have all manner of problems, particularly they are too easily ignored.Tashia
Yes, constructor should either construct a valid object or throw an exception.Arpeggio
@Antoine That is a horrible idea, and has repeatedly been shown to have many, many problems, Just use exceptions.Tashia
@NeilButterworth Okay sorry ^^. That is why I am looking for a better answer :).Lobel
@Antoine A better answer than what? If construction fails, throw an exception - what could be simpler?Tashia
@NeilButterworth According to isocpp.org, you may not have the option to use exceptions. But i have no idea why exception should be disabled or a reason to do that. I mean, every one should be using pretty modern compilers, right ?Sordino
@Hatsu "According to isocpp.org, you may not have the option to use exceptions." - that's typically because of extremely restrictive coding standards (e.g. Google) - nothing to do with compiler technology.Tashia
@HatsuPointerKun: Some embedded platforms, real-time-systems, or high-reliability-systems either lack the support, or are programmed with a coding-standard forbidding exceptions.Bushhammer
@NeilButterworth Yes, thanks for an example of a bone-headed coding-standard.Bushhammer
And as soon as you find yourself with a no exceptions coding standard, odds are it's time to pull out your trusty old C text because what they're really saying is we want C code.Flowerlike
S
12

According to isocpp.org, the proper way to handle the failure in a constructor in C++ is to :

Throw an exception.

It's not possible to use error code since constructors don't have return types. But :

If you don’t have the option of using exceptions, the “least bad” work-around is to put the object into a “zombie” state by setting an internal status bit so the object acts sort of like it’s dead even though it is technically still alive.

But you should really use exceptions to signal failure in constructors if you can, as said :

In practice the “zombie” thing gets pretty ugly. Certainly you should prefer exceptions over zombie objects, but if you do not have the option of using exceptions, zombie objects might be the “least bad” alternative.

Sordino answered 11/8, 2017 at 21:24 Comment(0)
W
2

By adding a layer of abstraction over your constructor, you can achieve your goal of checking to see if a constructor has failed and return an error code. By using a Initialize-Shutdown pattern, you can ensure that if an error occurs somewhere in your constructor, you'll still have access to your object to cleanup anything that you allocated. Here's an example:

// This model we can potentially throw an exception from the constructor
class Keyboard {
private:
    Device* mDevice;
public:
    Keyboard() { 
        mDevice = ConnectToDevice();
        mDevice->Init(); // Say this can throw exception } // If exception thrown, the destructor is not called and memory is leaked
                                                          // The object no longer exists, no way to clean up memory

    ~Keyboard() { DisconnectFromDevice(mDevice); } // DisconnectFromDevice() cleans up some dynamically allocated memory
};

// In this model we use a Initialize-Shutdown pattern
class Keyboard {
private:
    Device* mDevice;
    bool IsInitialized = false;
public:
    Keyboard() {} 
    ~Keyboard() { DisconnectFromDevice(mDevice); } // DisconnectFromDevice() cleans up some dynamically allocated memory
    void Initialize() {
        mDevice = ConnectToDevice();

        if (this.mDevice == nullptr)
            status = -1;

        mDevice->Init(); // Caller still needs to catch exception if it throws
                        // If exception is thrown here, the caller is responsible for cleaning up
                        // However, the object is still alive so caller can manually call or other cleaning method
        
        IsInitialized = true;
        
        return;
    }
    void Shutdown() {
        if (IsInitialized)
            DisconnectFromDevice(mDevice);

        return;
    }
};
Wandis answered 19/12, 2018 at 18:35 Comment(1)
This is the idiomatic solution to the problem in embedded programming where people often do not enable c++ exception handling. Though, usually, Initialize() returns a status value like "bool" to indicate success.Secretarygeneral
B
1

There is no way to do this. The best way would probably be to have a static init() method that would return an instance of the class and make the constructor private. You could do most of the constructing from the init method, and just return an error code from that.

Brownfield answered 11/8, 2017 at 21:21 Comment(3)
Unfortunately an init() method doesn't work for many classes, particularly those having member variables with constructors that take parameters.Tashia
Not saying I agree with the answer but for the record the init function could take perfectly forwarded variadic template argumentIleana
@Ileana An init() function, with whatever parameters, must be called in the body of a constructor,and so cannot initialise anything - it can only assign.Tashia
B
0

Depends on how likely you deem such an error and how critical a proper initialization is for further program execution.

  • If a failure to initialize the object is considered an exceptional case (e.g. because you ran out of memory) then throw an exception.
  • If it is an expected failure with wich you want to deal right here and now, set the object into a failed/default constructed state and allow the user to query for the error code (e.g. like when std::fstream isn't able to open a file). This is especially helpful if you want to retry the object initialization with different parameter values.

Btw.: If you decide on using an exception, I'd probably not turn it into an error code unless you absolutely must. Exceptions where specifically designed, such that you don't have to pass error codes around manually.

Benghazi answered 11/8, 2017 at 21:43 Comment(9)
You can't retry object initialisation, at least not on the same object.Tashia
The downside of case 2 is now you have to test, "Have you been properly initialized?" every time before you use the object. Gets to be a real bummer.Flowerlike
@NeilButterworth: Maybe not in the standard sense, but I'm talking about initialization into a useful state (e.g. if you consider farm, you can first try to construct it with a user provided file name and if that failed, you might want to fall back to a default file which you access by using open)Benghazi
Initialisation has a specific meaning in C++. It's not a good idea to re-use such meanings, so you might want to re-word your answer.Tashia
@user45: Yes, but sometimes that is the more natural thing to do.Benghazi
@NeilButterworth: Ok, so what would you call e.g. the effect of an int function?Benghazi
I guess you mean init(). I really don't know, as I would never use such a thing. Perhaps "default re-assignment"?Tashia
int a = 42; Initialization. int a; a = 42; assignment. MyClass a; Initialization. MyClass a; a.init(); a wrapper for some assignments.Flowerlike
@Neil: Yes I meant init() and it is just a generic placeholder name for something like open() (since c++11 I tend to rather implement this via parameterized constructor+move assignment operator). I'll see if I can come up with a name that is not confusing for beginners and will satisfy language layers ;).Benghazi
G
0

There is nothing preventing you from returning an error code from a constructor by throwing an exception.

You just need to derive your own exception class from std::runtime_error:

#include <stdexcept> // for std::runtime_error
#include <windows.h> // for HRESULT

class com_error : public std::runtime_error
{
    HRESULT hr;
public:
    com_error(const char *_Message, const HRESULT _hr)
    : std::runtime_error(_Message)
    , hr(_hr)
    {
    }

    HRESULT error_code() const noexcept
    {
        return hr;
    }
};

Now you can throw an exception which contains error code:

class A
{
public:
    A()
    {
        HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
        if (SUCCEEDED(hr)) {
            // TODO: continue initialization
        } else {
            throw com_error("COM initialization failed", hr);
        }
    }
};

And you can catch it and examine the error code:

try {
    A *a = new A();
    // ...
} catch (com_error& e) {
    HRESULT hr = e.error_code();
    // TODO: Do what you want with error code here
}

That doesn't mean it is a good idea. It is better to use Initialize / Shutdown pattern when the object construction is complex and might require cleanup / rollback.

Gaylegayleen answered 19/11, 2021 at 14:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.