0

Using std::string_view in following scenario:

struct A : public std::exception{
    A (const char* c) : v_(c){}
    const char* what() const noexcept override;

private:
    std::string_view v_;
};

The above idea works fine and now the copy-ctor will by default be noexcept (which is great for an exception type!) - and the string is also 'verified' at the call site.

However, the thing thats left is that semanticly a string_view is not gaurenteed to be zero terminated (although in this case we wrote the code so that it will be - it would be best if that was guarenteed for this particular case like it only had the constructor that we actually used.

Im considering if something like the following be a good work-around ?

struct c_string_view {
  c_string_view(const char* c) : sv_{c}{};
  const std::string_view sv_;
};

But I wanted to know if anyone else had this issue (and what they did) or if im overlooking something simpel ?

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
darune
  • 10,480
  • 2
  • 24
  • 62
  • 1
    Is there a reason you don't store a `const char*` rather than a `std::string_view`? – Galik Oct 18 '19 at 12:32
  • 1
    `std::exception::what() noexcept` isn't a `virtual` method so you can't `override` it. It must be `const` to be `override`-able. – Ted Lyngmo Oct 18 '19 at 12:39
  • 3
    Personally I'd just take an store a std::string. The cost of an allocation isn't going to be noticed compared to the cost of throwing. If you are out of memory you have bigger things to worry about. This comes with the benefit of not having to worry about the lifetime of the thing passed to the exception. If you're really concerned about being able to allocate the string, you could have a static member and pre-reserve space and use that so you don't have an allocation during the exception. – NathanOliver Oct 18 '19 at 12:41
  • 2
    IMHO using `std::string_view` in excpetion is inherently flawed design and you should rather use good 'ol `std::string` – bartop Oct 18 '19 at 12:42
  • very similar quesiton: https://stackoverflow.com/questions/58380250/is-there-a-safe-way-to-assert-if-a-string-view-is-null-terminated – 463035818_is_not_an_ai Oct 18 '19 at 12:48
  • @NathanOliver good point - although low memory conditions can happend occasionally (in particular if the software itself is capable of using a lot of memory) - perhaps unique_ptr would not be so bad – darune Oct 18 '19 at 13:02
  • @bartop problem with good 'ol string is that it throws on copy... – darune Oct 18 '19 at 13:03
  • 1
    Really I wouldn't worry about it unless you are using *all* of your ram. A string is small. Depending on what your message is your not even going to come close to using a KB for space. You can get a lot of words in a KB of a space and the chances of you not having a KB of space are pretty much nill. – NathanOliver Oct 18 '19 at 13:04
  • @Galik that could work, the only problem is that the string isn't 'validated' at the call site (think about a string that isn't null-terminated) – darune Oct 18 '19 at 13:04
  • @NathanOliver "out of ram" sitatutions do happend - including debugging memory related bugs on your own systems... – darune Oct 18 '19 at 13:11
  • regarding your reply to @Galik, if you pass non-null terminated string as const char* to string_view, you've gone wrong already. – Zaffy Oct 18 '19 at 13:17
  • @darune In OOM situation you should be just prepared for graceful (as possible) exit. Sorry, but this situation is not really common with virtual memory and when it happens you have your hands tied anyway – bartop Oct 18 '19 at 13:19
  • @bartop this depends to some degree on the type of program you are dealing with - eg. I would expect OS to keep functioning at least and also some types of user space programs even when out of ram. I understand that a lot of programs do not handle this situation for obvious reasons. – darune Oct 18 '19 at 13:23
  • True, in my actual code nullptr is special situation – darune Oct 18 '19 at 13:25
  • @darune right, that's also why OSes do not use exceptions, just error codes and stuff like that – bartop Oct 18 '19 at 13:38
  • @DavisHerring It has to do with the signature of the method to be overridden. The base class method is `const` and `std::exception::what() noexcept` is not. – Ted Lyngmo Oct 19 '19 at 09:43
  • ... which was the signature in OP:s post at the time I wrote the comment. It's changed now. – Ted Lyngmo Oct 19 '19 at 09:53

1 Answers1

3

Im considering if something like the following be a good work-around ?

Const members are usually problematic. They prevent assignability, and they prevent move construction. That said, assignability might not always be needed for an exception type, and the class in question is fast and noexcept to copy anyway.

So the drawbacks may not be so significant in this case, but neither are the advantages. Your class already has pre-condition in the constructor for the null termination, and the access is encapsulated to member functions (and friends) of A.


More fundamentally however, your exception class is highly prone to misuse. Consider following typical scenarios:

throw A(some_local_string.c_str());

char local_buffer[N];
// fill local_buffer with formatted message
throw A(local_buffer);

These result in undefined behaviour.

Why is that more prone to misuse than eg. std::string_view

It is in fact the use of std::string_view that makes your exception class prone to misuse. Storing a const char* would have the same problem.

std::string_view (and const char*) is fine for function arguments for example, when the function doesn't store the view for longer than the caller of the function exists. But exceptions are pretty much solely used so that they live longer than their callers because they are pretty much always thrown, which unwinds the caller.

if I/we had c_string_view from the get go - we could just use that in the constructor instead of const char* to show intent as well.

Accepting c_string_view doesn't really solve the problem since it doesn't by itself imply that it will be stored any more than const char* would. Best you can do is to document how the class can be used, which is somewhat unsatisfactory when those uses exclude common patterns of exception usage.

Maybe if you name the class static_string_exception or similar to make the limitations more obvious.


from commenters:

I'd just take an store a std::string ...

.. you should rather use good 'ol std::string

std::string is not an ideal solution either. It would prevent the copy constructor / assignment from being non throwing (not strictly, but other alternative is program termination), which would not be good for an exception type. For example, standard exception types are not allowed to have throwing copy constructor / assignment.

Once you reach for the copying route, you might as well use std::runtime_error which does that for you.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Good point about constness of members. About the misuse though: Why is that more prone to misuse than eg. std::string_view - thats actually a big point you raise there that I guess wasn't clear from the question (sorry) - if I/we had c_string_view from the get go - we could just use that in the constructor instead of const char* to show intent as well. – darune Oct 18 '19 at 12:57
  • @darune `string_view` has the same misuse-ability. Since it doesn't own the data the caller needs to know that and make sure they don't pass a temporary to it. – NathanOliver Oct 18 '19 at 12:59
  • I found that std::shared_ptr has noexcept copy and could serve as a simpel no-brainer solution – darune Oct 18 '19 at 14:24
  • I tried that at first. I realize may question may be not so good - im converting from using a const char* as an error type(a typedef, and it's too much work atm. to replace atm.), that may be returned from functions, initialized as zero, etc. - and thrown – darune Oct 18 '19 at 14:48