1

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.

cubuspl42
  • 7,833
  • 4
  • 41
  • 65
  • 1
    This is an ostensibly great question but will actually elicit a lot of debate. It's one of those religious issues. – djechlin Aug 05 '12 at 15:57
  • 1
    Also, do not use throw specs. For what I can only assume are idiotic reasons they are checked at run time and will lead to std::unexpected and hence crashes if violated (useful in a debug/development setting but not in a production setting), which is literally the opposite of what you want an exception to do. – djechlin Aug 05 '12 at 16:00
  • 1
    @djechlin good point. Furthermore, exception specs are deprecated in C++11. – juanchopanza Aug 05 '12 at 16:39
  • I expect this question is going to get locked. The code as posted has a lot of transgressions against best practices. Exception specifications are pure evil, and have been deprecated. Error codes force the user to learn about your arbitrary decisions. Avoid them when possible. Use something derived from std::exception rather rolling your own from scratch, for the same reason. There's more. – Jive Dadson Aug 05 '12 at 18:58
  • @Jive Dadson _"Exception specifications are pure evil"_ Thanks, I'll never use them again. _"Error codes force the user to learn about your arbitrary decisions."_ This library is designed to support map files format of one old Monolith's game engine. Some strings in the header cannot contain some special characters, and string has to be shorter than X bytes. How can I notify the user about it without eiter returning error code, or throwing exception? _"Use something derived from std::exception"_ I'll do that, thanks. – cubuspl42 Aug 05 '12 at 21:14
  • 1
    @cubusl42 I think maybe you do not know what an exception specification is. Those are what are pure evil, not exceptions themselves. The exception specification is on this line: "void MyString::set2(const std::string& s) throw(exception)" – Jive Dadson Aug 06 '12 at 18:40
  • @Jive Dadson Oh, so you just think that throwing an exception is the best I can do in this situation (just without the specification). If that's what you mean, it's quite constructive, thanks. BTW, I think that a user still has to learn about a few arbitrary decisions of mine in order to catch the thrown exception, doesn't he? – cubuspl42 Aug 06 '12 at 21:48

2 Answers2

1

In general in C++ it's recommended to use exceptions to indicate errors unrecoverable in the current context. But it depends on purpose. You may want to compile your library in an embedded environment using no exceptions (for sake of efficiency), then you have to go with return codes.

Its easy to wrap an API using return codes to one that uses exceptions, but no way vice versa.

EDIT:

Some more reasoning why it may make sense not to use exception handling:

Exception handling usually introduces additional information about try/catch blocks necessary to be placed in the call stack + some performance penalty to build and check these informations.

See also: performance of C++0x exceptions

Community
  • 1
  • 1
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • If an error is "unrecoverable," there is no need for error codes or throwing an exception. Call punt() and be done with it. – Jive Dadson Aug 09 '12 at 03:44
  • A try-block is for a situation where "all this has to finish up, or else just unwind the stack and get me back to the catch-block." Usually the condition is indeed exceptional. However I remember exactly one application in all my decades of programming where it was normal to finish a task when the stack was deep with frames that were no longer needed. The unusual case was to run to completion (and then give up in defeat). For that function, throw("Success") was the desired outcome, and normal. – Jive Dadson Aug 09 '12 at 03:56
  • @JiveDadson I've changed the wording a bit, I think this is more comprehensive what I mean with 'unrecoverable'. – πάντα ῥεῖ Aug 09 '12 at 07:50
1

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&LT;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.

Jive Dadson
  • 16,680
  • 9
  • 52
  • 65
  • Ok, I've just read some info about standard exceptions and they are suitable for my needs. I just think that _std::length_error_ is a bit more accurate, because I can't accept statement like `mapObject.name = "this name is too long for a map name!"` ('name' is a property). – cubuspl42 Aug 10 '12 at 14:59