1
\$\begingroup\$

I've looked at boost::exception and realized it is too advanced for my project, as my colleagues mostly have legacy C background. I do not need arbitrarily complex information in exception, just some error messages and trace info that can be written to process log.

Someone on Stack Overflow wrote such code as an example, but his solution can throw while trying to insert additional exception info (looks dangerous to me, correct me if I am wrong).

I tried to avoid using anything that can throw (vector of strings) inside the exception object. See: usage example.

Your feedback in term of correctness, ease of use, and required additional functionality is welcome.

const unsigned int long_enough = 512;
class my_exception : public exception {
    std::array<char, long_enough> err;
    std::array<char, long_enough>::iterator insertion_point;
public:
    template <typename Iter>
    void append(Iter first, Iter last) noexcept {
        //Remember null terminator!
        auto space_left = std::distance(insertion_point, err.end());
        if (std::distance(first, last)>=space_left){
            last = first + (space_left - 1);
        }
        insertion_point = copy(first, last, insertion_point);
    }
    void append(const char msg[]) noexcept {
        append(msg, msg+strlen(msg));
    }
    my_exception(const char msg[]) noexcept {
        insertion_point = err.begin();
        append(msg, msg+strlen(msg));
    }
    virtual const char* what() const noexcept override{
        return err.data();
    }
};
\$\endgroup\$
3
\$\begingroup\$

A note on the the link you posted; the OP was asking a hypothetical question in regards to what happens ('under the hood') to the call stack/exception/etc. if the exception that is caught is of a reference type (i.e. catch (std::exception& e) vs catch (const std::exception& e)). As was answered there, yes it is not a good idea to modify the exception within a catch (though completely legal code, still not advised).

Per your code, there are a couple of things worth noting: changing your err type from a std::array<char, long_enough> to a std::string would simplify all of your code, also, while you can pass a const char[] into functions, it decays into a pointer (pointing to the first element in the array), so it's similar to just const char*, if you're intent WAS to pass in a reference to a char[], it's recommended you pass a const size, as in template<int N> someFn(const char (&x)[N]), otherwise stack issues can occur. As well, if you're working with C++, unless you have a specific need to use a char[] (which is possible) it's advised to use a std::string over a char[].

Lastly, while having an append function to add aditional information into your exception object could be useful, it's usually better to derive a specific exception from your base exception with specific information in it (or otherwise just put the needed information in your throw exception...). Here's you're exception class with the err variable being a std::string

class my_exception : virtual public std::exception {
private:
    std::string err;
public:
    void append(std::string::iterator first, std::string::iterator last) {
        err.append(first, last);
    }
    void append(const char* msg) {
        err.append(msg);
    }
    my_exception(const char* msg) noexcept : err(msg) {}
    virtual const char* what() const throw() {
        return err.c_str();
    }
};

Use it like such:

try {
    throw my_exception("here's a simple message");
} catch (std::exception e) {
     e.append("...added some text");
     std::cout << "error: " << e.what() << std::endl;
     // or printf("error: %s\n", e.what()) for your C friends
}

As a side note (and for reference), here is the exception code from part of an open source framework I'm working on (just in a single header file exception.hpp):

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

namespace your_namespace {
    class exception : virtual public std::exception {
        public:
            exception() throw() : m_what("general exception") {}
            exception(const char *reason) throw() : m_what(reason) {}
            exception(const std::string& reason) throw() : m_what(reason) {}
            virtual ~exception() throw() {}
            virtual const char* what() const throw() { return m_what.c_str(); }
            virtual void seterr(const char *err) { this->m_what = std::string(err); }
            virtual void seterr(const std::string &err) { this->m_what = err; }
            friend std::ostream& operator<<(std::ostream& s, const omni::exception& e)
            { s << e.m_what; return s; }
            friend std::basic_ostream<wchar_t>& operator<<(std::basic_ostream<wchar_t>& s, const omni::exception& e) {
                s << e.m_what.c_str();
                return s;
            }
        protected:
            std::string m_what;
    };

    class some_other_exception : public your_namespace::exception {
        public:
            some_other_exception() : exception("some other exception happened") {}

    };
}

To use:

try {
    throw your_namespace::exception();
} catch (std::exception e) { // or const std::exception&
    std::cout << "error: " << e << std::endl;
    // or printf("error: %s\n", e.what()) for your C friends
}

try {
    throw your_namespace::exception("Here's an exception for you");
} catch (std::exception e) {
    std::cout << "error: " << e << std::endl;
}

try {
    throw your_namespace::some_other_exception();
} catch (std::exception e) {
    std::cout << "error: " << e << std::endl;
}

I hope that can help

\$\endgroup\$
1
  • \$\begingroup\$ @Deduplicator, I've edited my post to be a little more clear, I was more trying to point out that if they wanted to pass the reference itself instead of the decayed pointer (that is char c[5]; someFn(&c) vs someFn(&c[0])) then their syntax would need changing. I've witnessed some C++ devs who use char[] as a function parameter to intend something like the following: someFn(char[]&) without understanding that a char[] type gets decayed to a pointer type in a function call. So someFn(char[]) and someFn(char*) are completely different types to these devs :/ Sorry for the confusion! \$\endgroup\$
    – txtechhelp
    Oct 20 '15 at 20:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.