Search code examples
c++exceptionreturn-valueconventions

Throwing exception versus returning a result code


I'm creating a library. I want to make a fixed-length string class.

#include <string>
#include <iostream>

#define OK 0
#define TOO_LONG 1
#define UNALLOWED_CHARACTERS 2

struct MyString {
    MyString(int l)
        : m_length(l) { }
    struct exception {
        exception(int t, MyString *p)
            : type(t), ptr(p) { }
        int type;
        MyString *ptr;
    };
    int set(const std::string& name);
    void set2(const std::string& name) throw(exception);

    std::string m_str;
    int m_length;
};

int MyString::set(const std::string& s)
{
    if(s.size() > 64) {
        return TOO_LONG;
    } else if(s.find('~') != std::string::npos) {
        return UNALLOWED_CHARACTERS;
    } else {
        m_str = s;
        return OK;
    }
}

void MyString::set2(const std::string& s) throw(exception)
{
    if(s.size() > m_length) {
        throw exception(TOO_LONG, this);
    } else if(s.find('~') != std::string::npos) {
        throw exception(UNALLOWED_CHARACTERS, this);
    } else {
        m_str = s;
    }
}

int main()
{
    using namespace std;
    //OPTION 1
    {
        MyString s1(10);
        MyString s2(10);
        int code;

        code = s1.set("abcdefghijX");
        switch(code) {
        case TOO_LONG:
            //handle <--
            break;
        case UNALLOWED_CHARACTERS:
            //handle
            break;
        default:
            //ok!
            break;
        }

        code = s2.set("abcdefghi~");
        switch(code) {
        case TOO_LONG:
            //handle
            break;
        case UNALLOWED_CHARACTERS:
            //handle <--
            break;
        default:
            //ok!
            break;
        }
    }

    //OPTION 2
    {
        MyString s1(10);
        MyString s2(10);
        try {
            s1.set2("abcdefghijX");
            s2.set2("abcdefghi~");

        } catch(MyString::exception &e) {
            cerr << "MyString::exception: ";
            auto p = e.ptr;
            if(p == &s1) cerr << "s1 ";
            else if(p == &s2) cerr << "s2 ";
            switch(e.type) {
                case TOO_LONG: cerr << "too long"; break;
                case UNALLOWED_CHARACTERS: cerr << "unallowed characters"; break;
            }
            cerr << endl;
        }

    }
}

I don't know which version of MyString::set() I should use. What is the convention in such cases? I used STL in this example for demonstration purposes.


Solution

  • It is a good idea to mimic the behavior of the standard library functions unless one has a specific reason not to. BTW, since tr1, STL has a fixed-length string class built in. Lets see what it does. The only example implementation I have handy is Visual C++ 2010.

    
    std::tr1::array<int,5> arry;
    arry[10] = 42; // Oopsie. There is no element 10.
    
    

    When compiled and run as the "Debug" version, I get an assert failure. When compiled for "Release" the offensive statement quietly does ... NOTHING. It is optimized right out of existence. Okay, maybe that is not always what one would want. Forget what I said about mimicking the STL, or at least Microsoft's implementation. Train of consciousness continues...

    I think it is fair to say that if the program tries to set an out of range cell, that is a logic error in the program. In mission-critical software it might be a good idea to have code in place to deal with a situation like that and recover from it, while trying like heck to make sure it can never, never happen.

    So the answer is, throw an exception of type std::out_of_range.

    So there.