Авто новостройки ostream

Заранее извиняюсь за плохое название, не уверен, как назвать то, что я пытаюсь сделать.

Некоторый фон, если вы не хотите читать, перейдите к следующему абзацу. У меня есть класс модульного теста, где я вызываю assert с некоторым условием, и, если он не выполняется, я вывожу некоторую строку, которая была передана. Я обнаружил, что довольно неудобно создавать строку для отправки в нее, если, например, я хочу сказать "Failed on index: " + i, Моя идея состоит в том, чтобы вернуть std::ostream вместо того, чтобы взять std::string, Если утверждение не удается, я возвращаюсь std::cerr, если утверждение проходит, то я возвращаю std::stringstream, Я думаю, я мог бы сделать все это просто отлично. Я должен был бы хранить std::stringstream в моем классе модульного теста, чтобы я мог вернуть ссылку.

То, что я хотел бы сделать, это вместо того, чтобы вернуть стандарт std::ostream вернуть расширенный std::ostream что выводит std::endl когда это сделано, мне не нужно запоминать это для каждого утверждения. В частности, идея заключается в следующем:

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

Идея заключается в том, что при уничтожении этот новый класс будет выводить конечную линию и будет уничтожен, как только он больше не будет использоваться (т.е. больше не будет). << операторы). Пока это то, что у меня есть (я удалил часть кода в assert, и он на самом деле внутри класса, но этого достаточно, чтобы показать, что происходит):

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());
}

Когда я пробую этот метод, я получаю некоторые вещи, в основном говорящие мне, что возвращать объект, который я только что создал, неправильно, потому что это не lvalue. Когда я пытаюсь изменить его так, чтобы он не возвращал ссылку, он жалуется, что нет конструктора копирования (вероятно, это потому, что я расширяю std::ostream и у него нет конструктора копирования).

То, что я ищу, это какой-то метод, который заставляет компилятор создать временный newline_ostream, который assert() запишет свой результат в тот, который умрет, как только он больше не будет использоваться (т.е. больше не будет << операторы). Возможно ли это и если да, то как?

4

Решение

копирование 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;
}
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
2

Это действительно зависит от специфики того, чего вы хотите достичь.

Если вы никогда не слышали о Тип Туннелирование, например, это может быть хороший момент, чтобы прочитать об этом. Есть способ использовать прокладки, чтобы делать сумасшедшие вещи …

В противном случае, вот простая версия:

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 не выполняется вообще.

1

Вы также можете использовать препроцессор для этого:

#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), являются в основном единственными альтернативами. Это связано с тем, что даже если вы начинаете работать с буферами, вы не можете сказать, когда выполняется одна операция вывода, а затем начинается следующая, поэтому вы не знаете, когда вставить новую строку.

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
0
По вопросам рекламы [email protected]