4

I read this and find out it's important to handle exceptions, i use nlohmann::json (from github) and nearly in most of my member functions is use nlohmann::json::parse and nlohmann::json::dump which make a chance to throw exceptions if the input has a problem.

So I need to handle those chance of throwing exception something like this:

bool my_class::function(const std::string& input) const
try
{
    using namespace nlohmann;

    const auto result = json::parse(input);
    const auto name = result["name"].dump();

    /* and ... */
}
catch (const std::exception& e)
{
    /* handle exception */
}

But i want to know which line of the code throw exception, so if i write something like this:

bool my_class::function(const std::string& input) const
{
    using namespace nlohmann;

    try
    {
        const auto result = json::parse(input);
    }
    catch(const std::exception& e)
    {
        /* handle exception */
    }

    try
    {
        const auto name = result["name"].dump();
    }
    catch(const std::exception& e)
    {
        /* handle exception */
    }

    /* and ... */
}

It left me with thousands of try-catch blocks. It's a better why to handle exception ?

ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
Kate
  • 561
  • 4
  • 11
  • I prefer the first, second gives so much problems with scoping and you make the actual intend of the program unreadable – JVApen Jun 16 '20 at 06:49
  • why do you need the exactly line of code where the json op fails? is the "what" info not enough in the catch block? – ΦXocę 웃 Пepeúpa ツ Jun 16 '20 at 06:50
  • 2
    Shouldn't the stack trace have information on which line the exception was thrown ? – Nextar Jun 16 '20 at 06:53
  • @ΦXocę웃Пepeúpaツ, No because if i use `nlohmann::json::dump()` multi time in my function, and one of them throwing exception i cant figure out which one was throw exception. – Kate Jun 16 '20 at 06:54
  • @Nextar, I want to catch them in runtime and give a proper message to user. – Kate Jun 16 '20 at 06:55
  • @Kate instead of using `json::dump()` you can write a macro which will rethrow exception with the line of code added. Then you only use the macro, no explicit try-catch. – freakish Jun 16 '20 at 06:56
  • You can also wrap the code in a single try/catch and keep track of the place an exception may be thrown by a variable (enum, string) that is updated after each successful parse. You can evaluate that in the catch block and see which one was the last successful operation. Ah, I see @ΦXocę웃Пepeúpaツ has had the same idea. – Peter - Reinstate Monica Jun 16 '20 at 06:59
  • By the way, this is not an untypical problem (and hence a good question). Note that traditional return value error handling, if desired with the same degree of granularity, is about as verbose (you would have `if (ret = parse() != SUCCESS) { cerr << ...; return ret; }` or the like for every library call). Unfortunately and paradoxically complete error handling tends to obfuscate the code. Your degree of detail is probably too high. Let the exception bubble up (that would be *less* verbose than return code diagnosis!) and handle it above. – Peter - Reinstate Monica Jun 16 '20 at 07:05
  • The second code doesn't even compile because `result` goes out of scope – M.M Jun 16 '20 at 07:07

2 Answers2

5

I would go like this: set a "sequence pointer" to keep the record of where/what are you trying to parse, like with a string with the pattern trying to parse a... so you can know/notify where exactly was the faulty element in the json.

look in the example below, if "name is faulty, then r is holding the value "trying to parse name" so in the exception you have the information about what json element is causing the problem :)

bool my_class::function(const std::string& input) const
{
    std::string r{""};
    try
    {
        using namespace nlohmann;
        r="trying to parse input";
        const auto result = json::parse(input);

        r="trying to parse name";
        const auto name = result["name"].dump();

        r="trying to parse age";
        const auto age = result["age"].dump();

        /* and ... */
    }
    catch (const std::exception& e)
    {
        /* handle exception */
    }
}
Kate
  • 561
  • 4
  • 11
ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
  • You might want to change r to `const char *`, so it becomes less expensive, assuming you have all string literals. – JVApen Jun 16 '20 at 06:59
  • thanks for the suggestion, this is only an example, op can optimize the code later as own requirements! – ΦXocę 웃 Пepeúpa ツ Jun 16 '20 at 07:02
  • 1
    Agreed, one could also use an int and use `__LINE__` if you only need the line number. At the same time, it can prevent copy paste mistakes. – JVApen Jun 16 '20 at 07:04
3

As you might have noticed, C++ doesn't have this information available. @ΦXocę웃Пepeúpaツ provides some good workaround.

Let me provide some other point of view, as an end-user of your program, I'm not interested in the line of code on which the program failed. Either the JSON I'm providing is correct or it's incorrect. In the second case, I want to know what I need to do in order to fix the JSON. Looking at the code defining the exceptions, this looks really detailed.

The moment you are interested in it is when you wrote a bug in your program and you are getting unexpected errors. At that point in time, you better attach a debugger to your program and step through it while breaking on the throw of any exception. This will give you not only the line number, though also all information available on the stack ... I can recommend writing unit tests on your code, so you have small pieces of code that you have to debug. Ideally, you can even reduce the failure case to a new unit test if you still encounter an uncovered bug in your program.

Finally, the argument of performance. Having more details requires to collect more details. This collecting comes at a cost. In other programming languages, like Java, you can ask a call stack to your exception, in C++, exceptions are at its bare minimum. Although keeping track of a line number might not be that expensive, it does require extra assembly instructions that ain't needed for your end-user.

In short, the language doesn't provide a convenient way to get the line number. This because there are better ways to get this information and much more: your debugger.

JVApen
  • 11,008
  • 5
  • 31
  • 67