[C++] туплю про ссылки и временные объекты

erotic

Есть такой код:
 
#include <iostream>
#include <sstream>
#include <time.h>

struct Logger
{
void log(const std::string& s) { std::cout << ::time(NULL) << " " << s << std::endl; }

class Streamer
{
friend class Logger;

Logger& parent;
std::ostringstream s;

Streamer(Logger& p) : parent(p) {}

Streamer(const Streamer& r) : parent(r.parent)
{
s.str(r.s.str;
}

public:
~Streamer
{
parent.log(s.str;
}

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

Streamer log { return Streamer(*this); }
};

template <typename BUF>
void debug(BUF& b)
{
b << "debug\n";
}


int main
{
Logger logger;
#if 1
debug(logger.log << "I'm ");
#else
debug(logger.log;
#endif
}

Суть в следующем: у нас есть логгер, который умеет за раз вывести строчку с какой-то дополнительной информацией типа времени, и есть объект Streamer, который по идее должен создаваться как временный только из логгера, в него что-то там выводится, и в конце выражения он уничтожается и выводит все в лог, например, так:

logger.log << "exception is " << ex.what;

Странная для меня штука заключается в том, что при наличии функции типа debug в моем коде я не могу просто так передать в нее этот Streamer, поскольку функция требует неконстантной ссылки, а объект у меня временный, но при этом я могу это сделать, если выведу в Streamer ну хотя бы пустую строку, и оператор вернет уже нормальную ссылку на объект.
Казалось бы, странное ограничение, и наверное его как-то можно обойти, не вызывая фейковый operator << . Понятно, что если я контролирую класс Streamer, я могу приделать к нему какую-то странную функцию типа

Streamer& ref { return *this; }

и вызывать так:

debug(logger.log.ref;

Но что, если изменять Streamer я не могу? Попытки кастовать пока не компилируются, например, такие:

debug(const_cast<Logger::Streamer&>(logger.log;
debug(const_cast<Logger::Streamer&>(&logger.log;

Maurog

объект Streamer, который по идее должен создаваться как временный только из логгера, в него что-то там выводится, и в конце выражения он уничтожается и выводит все в лог
кривой дизайн, ибо логика в деструкторе и есть неконтролируемый процесс создания копий объектов. как следствие однажды получишь в логах пустые строчки, которые ты не надеялся там увидеть. с копированием можно пободаться, введя move конструктор и запретив копирование. в деструкторах же надо как минимум гарантировать отсутствие исключений, иначе приложуха может падать при double exception
в целом, странно, что оно у тебя компилируется, ибо конструктор копии недоступен (приватен). см пример: http://ideone.com/7HF5Ml
Казалось бы, странное ограничение, и наверное его как-то можно обойти, не вызывая фейковый operator <<
действительно, странное, но такие уж правила игры. чуток недавно обсуждали здесь:
если я контролирую класс Streamer, я могу приделать к нему какую-то странную функцию
можно еще навесить const на метод << , объявить s как mutable и передавать const ссылку в функцию debug
Попытки кастовать пока не компилируются
попробуй через могучий "оператор" ZZZ : http://ideone.com/3iTTHi
вроде даже должно работать :grin:

erotic

кривой дизайн, ибо логика в деструкторе и есть неконтролируемый процесс создания копий объектов. как следствие однажды получишь в логах пустые строчки, которые ты не надеялся там увидеть. с копированием можно пободаться, введя move конструктор и запретив копирование. в деструкторах же надо как минимум гарантировать отсутствие исключений, иначе приложуха может падать при double exception
Можно примеры этого "однажды", которое я получу? Мне кажется, что способ использования объекта довольно ограничен, чтобы получить нечто неожиданное.
С исключениями в деструкторе легко побороться, проверяя std::uncaught_exception но это не тема моего вопроса.
С копированием не надо бодаться, т.к. оно нужно только при вызове Logger::log, когда строка в объекте пустая, там почти ничего не копируется.
в целом, странно, что оно у тебя компилируется, ибо конструктор копии недоступен (приватен)
Там friend стоит, иначе было бы странно, что объект вообще создается, конструктор же тоже приватный.
можно еще навесить const на метод << , объявить s как mutable и передавать const ссылку в функцию debug
И еще навесить const_cast на возвращаемый *this, что в целом производит отвратительное впечатление.
попробуй через могучий "оператор" ZZZ
С ZZZ работает, спасибо.

Maurog

И еще навесить const_cast на возвращаемый *this
навешивать не понадобится : надо вернуть const ссылку на объект

Werdna

debug(logger.log;
Happy debugging?
Нельзя так писать.

erotic

Поясни, почему нельзя. Если тебе название "debug" не нравится, так это просто пример функции, которая хочет на вход ссылку на какой-нибудь стример получить.

Werdna

Поясни, почему нельзя.
Мне что-то подсказывает, что debug — это макрос, который в сборке релизной не сделает вызов.

Maurog

Можно примеры этого "однажды", которое я получу?
дело в том, что конструкторы копирования - это спец методы и в некоторых сценариях компилятор волен (стандарт явно это оговаривает) вызывать их неограниченное число раз, даже если там есть сайд-эффекты. об этом можно почитать, например, тут: http://en.wikipedia.org/wiki/Return_value_optimization . там даже есть пример, который печатает разные вещи в зависимости от того, как отработал компилятор.
конкретно твой пример даже на практике (а не только в теории) может выдавать разные вещи
я играл тут: http://coliru.stacked-crooked.com/
при таком запуске
g++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
имеем выхлоп
1410247766 debug
а при таком запуске
g++ -std=c++11 -O2 -Wall -fno-elide-constructors -pedantic -pthread main.cpp && ./a.out
имеем выхлоп
1410247191
1410247191 debug
если тебе важно качество кода и отсутствие wtf от коллег в будущем, то желательно учитывать подобные нюансы языка
как метод решения я бы предложил
1) убрать определение конструктора копирования (объявление при этом следует оставить). в этом случае. если компилятор все же попытается заюзать конструктор, то линковка сломается (грязновато, но как страховка сойдет)
2) поддержать неограниченное кол-во копий (для этого можно stringstream держать по указателю и при копированиии шарить этото указатель)
3) поддержать move и запретить копирование. есть гарантии, что рабочий объект будет всего один (даже если move вызвался много раз а пустышки не должны пытаться что-то напечатать в деструкторе
с деструкторами дольше разжевывать, проще тебе погуглить :grin:
Оставить комментарий
Имя или ник:
Комментарий: