4

Sorry in advance for the bad title, not sure what to call what I'm trying to do.

Some background, if you don't care to read, skip to the next paragraph. I have a unit test class where I call assert with some condition and if it fails I output some string that was passed in. I have found that it is quite annoying to build a string to send to this if for instance I want to say "Failed on index: " + i. My idea is to return a std::ostream instead of taking a std::string. If the assert fails I return std::cerr, if the assert passes then I return a std::stringstream. I imagine I could do all of this just fine. I'd have to store a std::stringstream in my unit test class so I can return a reference.

What I would like to do is instead of returning a standard std::ostream return an extended std::ostream that outputs std::endl when it's done so I don't have to remember it for each assert. In particular the idea is the following:

UnitTest("My test");
ut.assert(false) << "Hello world";
ut.assert(1 == 0) << "On the next line";

The idea being that on destruction this new class would output the endline and that it would be destructed as soon as it was no longer used (i.e. no more << operators). So far this is what I have (I've removed some of the code in assert, and it's actually inside a class, but this is sufficient to show what's going on):

class newline_ostream : public std::ostream
{   
    public:
    newline_ostream(std::ostream& other) : std::ostream(other.rdbuf()){}
    ~newline_ostream() { (*this) << std::endl; }
};

newline_ostream& assert(bool condition, std::string error)
{
    if(!condition)
    {
        return newline_ostream(std::cerr);
    }

    return newline_ostream(std::stringstream());
}

When I try this method I get some stuff basically telling me that returning an object I just created is wrong because it's not an lvalue. When I try changing it to not return a reference it complains that there's no copy constructor (presumably this is because I'm extending std::ostream and it doesn't have a copy constructor).

What I'm looking for is some method that causes the compiler to create a temporary newline_ostream that assert() will write it's result into that will die as soon as it's no longer used (i.e. no more << operators). Is this possible and if so how?

4

5 回答 5

2

复制std::cerr是不可能的(复制构造函数std::basic_ostream被删除)。因此,创建一个实现复制构造函数的派生类并不是一个真正的选择。

我建议您创建newline_ostream一个包含对(而不是派生自)的引用的类std::ostream

#include <iostream>

class newline_ostream
{
  std::ostream &_strm;
public:

  explicit newline_ostream(std::ostream &strm)
    :_strm(strm)
  {}

  /* In the destructor, we submit a final 'endl'
     before we die, as desired. */
  virtual ~newline_ostream() {
    _strm << std::endl;
  }

  template <typename T>
  newline_ostream &operator<<(const T& t) {
    _strm << t;
    return *this;
  }
};

int main()
{
  newline_ostream s(std::cerr);
  s << "This is a number " << 3 << '\n';

  /* Here we make a copy (using the default copy
     constructor of the new class), just to show
     that it works. */
  newline_ostream s2(s);
  s2 << "This is another number: " << 12;

  return 0;
}
于 2012-10-28T05:56:24.740 回答
2

可能是异端,但是为了派生一个流以产生另一种流类型,更通用的方法可以是定义一个操纵器:

// compile with g++ -std=c++11 -Wall -pedantic

#include <iostream>

class sassert
{
public:
    sassert(bool b) :ps(), good(b) 
    {}

    friend std::ostream& operator<<(std::ostream& s, sassert&& a)
    { a.ps = &s; if(a.good) s.setstate(s.failbit); return s; }

    ~sassert()
    { 
        if(good && ps)  ps->clear(); 
        if(!good && ps) *ps << std::endl; 
    }

    //move semantics allow sassert to be a result of a calculation
    sassert(sassert&& s) :ps(s.ps), good(s.good) { s.ps=nullptr; }
    sassert& operator=(sassert s){ ps=s.ps; good=s.good; s.ps=0; return *this; }
private:
    std::ostream* ps;
    bool good;
};

int main()
{
    std::cout << sassert(false) << "this is a failed assertion";
    std::cout << sassert(true) << "this is a good assertion";
    std::cout << sassert(false) << "this is another failed assertion";
    std::cout << sassert(true) << "this is another good assertion";
    return 0;
}

将运行生产

this is a failed assertion
this is another failed assertion
于 2012-10-28T08:19:27.127 回答
1

这实际上取决于您想要实现的具体目标。

例如,如果您从未听说过Type Tunneling,那么现在可能是阅读它的好时机。有一种方法可以使用垫片做一些疯狂的事情......

否则,这是一个简单的版本:

class AssertMessage {
public:
    AssertMessage(): _out(nullptr) {}
    AssertMessage(std::ostream& out): _out(&out) {}

    AssertMessage(AssertMessage&& other): _out(other._out) { other._out = nullptr; }

    AssertMessage& operator=(AssertMessage&& other) {
        if (_out) { _out << "\n"; }
        _out = other._out;
        other._out = nullptr;
        return *this;
    }

    ~AssertMessage() { if (_out) { _out << "\n"; } }

    template <typename T>
    AssertMessage& operator<<(T const& t) {
        if (_out) { *_out << t; }
    }

private:
    std::ostream* _out;
}; // class AssertMessage

请注意如何通过嵌入指针我们不需要全局“空”对象?这是指针和引用之间的主要区别。还要注意使用移动构造函数/移动赋值运算符来避免输出2 个或更多换行符。

然后您可以编写assert方法:

AssertMessage UnitTest::assert(bool i) {
    return i ? AssertMessage() : AssertMessage(std::cerr);
}

但是....如果我是您,我会认真考虑使用宏,因为您可以获得额外的好处:

#define UT_ASSERT(Cond_) \
    assert(Cond_, #Cond_, __func__, __FILE__, __LINE__)

AssertMessage assert(bool test,
                     char const* condition,
                     char const* func,
                     char const* file,
                     int line)
{
    if (test) { return AssertMessage(); }

    return AssertMessage(std::cerr <<
        "Failed assert at " << file << "#" << line <<
        " in " << func << ": '" << condition << "', ");
}

然后你会得到类似的东西:

Failed assert at project/test.cpp#45 in foo: 'x != 85', <your message>

在大型测试套件中,拥有文件名和行号(至少)是非常宝贵的。

最后,一个宏可以让您获得更多:如果您在消息中调用一个函数,例如ut.assert(x) << x.foo();,那么x.foo()即使消息不会被打印,也需要完全评估;这是相当浪费的。但是使用宏:

#define UT_ASSERT(Cond_, Message_) \
    while (!(Cond_)) { std::cerr << .... << Message_ << '\n'; break; }

然后如果条件评估为根本不执行true的主体。while

于 2012-10-28T11:37:21.453 回答
0

您还可以为此使用预处理器:

#define U_ASSERT(ut, cond, stream) \
    do { ut.assert(cond) << stream << std::endl; } while (0)

U_ASSERT(ut, 1 == 0, "The result is " << (1 == 0));

但是,这个和您已经使用的方法(经过 jogojapan 修改)基本上是唯一的选择。这是因为即使您开始使用缓冲区,您也无法判断一个输出操作何时完成以及下一个操作何时开始,因此您不知道何时放入换行符。

于 2012-10-28T05:59:53.993 回答
0

我找到了一种对我有用的方法(特别是我返回一个副本而不是参考):

class newline_ostream : public std::ostream
{   
    public:
    newline_ostream(const std::ostream& other) : std::ostream(other.rdbuf()){}
    newline_ostream(const newline_ostream& other) : std::ostream(other.rdbuf()){}
    ~newline_ostream() { (*this) << std::endl; }
};

newline_ostream assert(bool condition, std::string error)
{
    if(!condition)
    {
        return newline_ostream(std::cerr);
    }

    return newline_ostream(nullStream); // nullStream defined elsewhere (in the class)
}

用法:

ut.assert(ps.getFaces()[i] == expected[i], ss.str()) << "Test " << i;

输出:

Test 0
Test 1
Test 2
Test 3
Test 5
Test 7
Test 9
Test 10
于 2012-10-28T06:03:30.537 回答