最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - How could I implement an assert function that support iostream - Stack Overflow

programmeradmin0浏览0评论

I would like to implement a CHECK() function that can work like this:

int a = 1;
int b = 2;
int c = 1;

CHECK(a == b) << "check failed" << endl; // this will abort the program and print "check failed"
CHECK(a == c) << "check failed" << endl; // this will do nothing since CHECK is valid

I do not know how could I abort the program but still print out something. Do you know how to implement some function that works like this?

I would like to implement a CHECK() function that can work like this:

int a = 1;
int b = 2;
int c = 1;

CHECK(a == b) << "check failed" << endl; // this will abort the program and print "check failed"
CHECK(a == c) << "check failed" << endl; // this will do nothing since CHECK is valid

I do not know how could I abort the program but still print out something. Do you know how to implement some function that works like this?

Share Improve this question asked Mar 2 at 10:38 Zeyu Zhang CNZeyu Zhang CN 1355 bronze badges 7
  • Perhaps return an object with a member if the check failed and an overloaded operator<<. The CHECK function returns this object. – gast128 Commented Mar 2 at 10:55
  • 1 I wouldn't. Using a macro like this is an extremely bad idea. If CHECK(a==b) expands to (a reference to) an object with an operator<<(), then your code streams "check failed" to that object, regardless of whether the assertion passes or fails (which seems a recipe for bugs in your error checking code that will be hard to find/resolve [a problem in code that, presumably, is required to check if some other code has failed] because you will be getting "check failed" written to some stream EVEN if the assertion succeeds). – Peter Commented Mar 2 at 11:15
  • In general you shouldn't add "logging for debugging" to your code. You should write unit-testable code and through testing just make sure it works. E.g. write all your code in a static library and then link two executables your production one and a (google test) unit test executable. Then if your code still has problems in production throw an excpetion, return an error or abort your code. – Pepijn Kramer Commented Mar 2 at 11:42
  • @Peter You can easily ignore the streamed input if assertion passes, see my answer. – Dominik Kaszewski Commented Mar 2 at 12:39
  • @DominikKaszewski That's not my point. If the assertion passes, then the input will be streamed and then discarded. That requires unnecessary machinery and complexity which (even with a low level of difficulty) takes effort to get right. It also means that it is not possible to remove the assertion testing from production code (e.g. for which NDEBUG is not defined) since making CHECK() a no-op (most desirable end state in production code, where it should be previously verified the assertions are true) is not possible. – Peter Commented Mar 2 at 23:19
 |  Show 2 more comments

3 Answers 3

Reset to default 2

The way gtest (among others) implements this exact pattern is to return a RAII object which executes all its code in destructor:

class check {
    public:
    explicit check(bool success) : success{ success } {}

    ~check() {
        if (!success) {
            abort_with_message(s.str());
        }
    } 

    check(const check&) = delete;
    check(check&&) = delete;
    check& operator=(const check&) = delete;
    check& operator=(check&&) = delete;

    template <typename T>
    check&& operator<<(const T& value) && {
        if (!success) {
            s << value;
        }
        return std::move(*this);
    }

    private:
    bool success{};
    std::stringstream s{};
};

#ifdef DEBUG
#define CHECK(predicate) check{ (predicate) }
#else
#define CHECK(predicate) check{ true }
#endif

Completing @DominikKaszewski 's answer

  • For io-manipulator as std::endl

    check&& operator<<(std::ostream& (*manip)(std::ostream&)) && {
        if (!success) {
            manip(s);
        }
        return std::move(*this);
    }
    
  • Avoid side effect when check passes.

    #define CHECK(predicate) (predicate) ? check{ true } : check{ false }
    

    Profiting than << has higher priority than :. So condition ? stream1 : stream2 << "text"; is parsed as condition ? stream1 : (stream2 << "text");

  • Fix compilation

    check(check&&) = default;
    

    as we use it explicitly :-)

Final code is:

class check {
public:
    explicit check(bool success) : success{ success } {}

    ~check() {
        if (!success) {
            abort_with_message(s.str());
        }
    } 

    check(const check&) = delete;
    check(check&&) = default;
    check& operator=(const check&) = delete;
    check& operator=(check&&) = delete;

    template <typename T>
    check&& operator<<(const T& value) && {
        if (!success) {
            s << value;
        }
        return std::move(*this);
    }

    check&& operator<<(std::ostream& (*manip)(std::ostream&)) && {
        if (!success) {
            manip(s);
        }
        return std::move(*this);
    }

private:
    bool success{};
    std::stringstream s{};
};

#define CHECK(predicate) (predicate) ? check{ true } : check{ false }

Demo

You don't need to reinvent the wheel. assert() itself has the ability to display error messages.

#include<cassert>
int a=1;
int b=2;
assert(a == b && "check failed"); // this will abort the program and print "Assertion failed: a == b && "check failed", file C:\Some\path\Source.cpp, line 42"

Generally speaking, if there is no reason not to, library functions should be preferred.

If you want to output something other than a string, you can put the output operation into assert() The disadvantage is that assert() will output unnecessary extra content.

assert(a == b || ((std::cerr << "a:" << a << ", b:" << b << '\n') && false));
/*
output: 
a:1, b:2
Assertion failed: a == b || ((std::cerr << "a:" << a << ", b:" << b << '\n') && false), file C:\Some\path\Source.cpp, line 42"
*/
发布评论

评论列表(0)

  1. 暂无评论