Как обнаружить, что ptr все еще ссылается на действительную ссылку после того, как эта ссылка выходит из сферы действия

Я немного кручусь с потоками и не могу опустить голову вокруг следующего.

Здесь у нас есть базовый ptr, который настроен на разные выходные потоки, будь то cout, cerr или file.

// ostream ptr
std::ostream* outstream;

// set output ostream
void setOutput(std::ostream & os)
{
  outstream = &os; 
}

// write message to ostream
void writeData(const std::string & msg)
{    
  *outstream << msg << '\n';
}

int main (int argc, char * const argv[]) 
{
  // init to std out
  setOutput(std::cout);
  writeData("message to cout");

  setOutput(std::cerr);
  writeData("message to cerr");

  std::ofstream fileout("test.txt", std::ofstream::out | std::ofstream::app);
  setOutput(fileout);
  writeData("message to file");
  //fileout.close();

  setOutput(std::cout);
  writeData("message2 to cout");

  return 0;
}

Вышеописанное прекрасно работает и показывает силу реализации iOS-образа С++. Отлично.

Однако, поскольку setOutput устанавливается по ссылке, объект, на который ссылается, должен оставаться в области видимости. Именно здесь возникает проблема. Я хочу выяснить способ вывода по умолчанию на std::cout, если поток или любой другой поток недействителен. То есть ссылочный объект выходит за пределы области видимости.

Например:

// write message to ostream
void writeData(const std::string & msg)
{
  if (/*stream or memory is invalid*/)
    setOutput(std::cout);

  *outstream << msg << '\n';
}
// local fileout goes out of scope
void foo()
{
  std::ofstream fileout("test.txt", std::ofstream::out | std::ofstream::app);
  setOutput(fileout);
  writeData("message to file");
}

int main (int argc, char * const argv[]) 
{
  setOutput(std::cout);
  writeData("message to cout");

  foo();
  /* problem the local fileout is no longer referenced by the ostream ptr*/
  /* the following should be redirected to std::cout cuz of default*/
  writeData("message2 to cout");

  return 0;
}

Вышеуказанное прекрасно, пока foo() не вернется к основной функции. Там это выглядит ужасно, потому что локально определенный ofstream недоступен.

Очевидно, это нецелесообразно, и пользователь должен это осознать. Однако я хочу обернуть все это в класс журнала и, таким образом, сохранить состояние объекта, даже считая, что это неправильное использование может случиться. Это приведет к недействительным нарушениям доступа, которые трудно найти.

Конкретный вопрос. Есть ли способ выяснить, все ли ptr или любой ptr по-прежнему ссылаются на действительный объект или ячейку памяти?

ps: Я мог бы использовать память кучи и делать что-то с умными указателями, но, откровенно говоря, я хотел бы сохранить его таким образом, если возможно

Ответы

Ответ 1

Конкретный вопрос. Есть ли способ выяснить, все ли ptr или любой ptr по-прежнему ссылаются на действительный объект или ячейку памяти?

Нет. Нет никакого способа понять это с помощью сырых указателей. Не в стандартном С++, по крайней мере.

Вам нужно будет гарантировать, что заостренный объект остается в живых до тех пор, пока он указывает на.

Общая схема, которая используется для обеспечения гарантии, представляет собой RAII, как описано в других ответах. Другим подходом к обеспечению достоверности указателя является использование интеллектуального указателя вместо исходного. Однако они не совместимы с автоматическими переменными.

Было бы хорошо продолжать указывать на мертвые объекты, пока вы могли бы гарантировать, что указатель не будет разыменован. Что часто трудно гарантировать, потому что, как уже было сказано, нет возможности проверить, существует ли заострённый объект.

Ответ 2

Это звучит как отличный вариант для RAII.

Напишите класс, который принимает имя файла и std::ostream** как параметры для своего конструктора. В конструкторе указанного класса создайте поток (как элемент) и установите указатель на поток. В деструкторе вернитесь к stdout.

Затем замените первые две строки следующей функции объявлением нового класса.

void foo()
{
  std::ofstream fileout("test.txt", std::ofstream::out | std::ofstream::app);
  setOutput(fileout);
  writeData("message to file");
}

Ответ 3

Вы должны использовать RAII, чтобы принудительно установить поток, а затем вернуться к std:: cout, если объект был уничтожен.

class OutputStream
{
    protected:
        static std::ostream*& internalGlobalStateOfOutputStream()
        {
            static std::ostream*  out = &std::cout;
            return out;
        }
    public:
        static std::ostream& getOutputStream()
        {
            return *internalGlobalStateOfOutputStream();
        }
};
template<typename T>
class OutputStreamOwner: public OutputStream
{
    T  ownedStream;
    public:
        OutputStreamOwner(T&& obj)
            : ownedStream(std::move(obj))
        {
            internalGlobalStateOfOutputStream() = &ownedStream;
        }
        template<typename... Args>
        OutputStreamOwner(Args... args)
            : ownedStream(args...)
        {
            internalGlobalStateOfOutputStream() = &ownedStream;
        }
        ~OutputStreamOwner()
        {
            internalGlobalStateOfOutputStream() = & std::cout;
        }
        // Delete copy
        OutputStreamOwner(OutputStreamOwner const&)           = delete;
        OutputStreamOwner& operator(OutputStreamOwner const&) = delete;
};

Использование:

void foo()
{
  OutputStreamOwner<std::ofstream>  output("test.txt", std::ofstream::out | std::ofstream::app);

  writeData("message to file");
}

Ответ 4

Возможным подходом является создание класса RAII, который переносит поток, прежде чем передавать его в setOutput. Этот класс должен быть разработан так, чтобы работать как shared_ptr, так что он поддерживает общий счетчик ссылок. writeData затем проверяет, имеет ли он единственную оставшуюся ссылку, и если это так, то уничтожает ostream и по умолчанию cout.

Ответ 5

Вы можете избежать этих осложнений в целом с помощью функции, которая принимает поток в качестве входных данных.

void writeData(std::ostream& os, const std::string & msg)
{    
     os << msg << '\n';
}

Вы можете дополнительно уточнить его, возвратив поток, чтобы можно было называть его:

std::ostream& os writeLine(std::ostream& os, const std::string & msg)
{    
     os << msg << '\n';
     return os;
}

// declare stream
stream << writeLine(stream, "Foo") << writeLine(stream, "Bar");

Фактически эта функция более удобна и удобна в обслуживании, так как вам не нужно помнить, какой поток установлен в любой момент времени. Для больших программ это важное качество.

Ответ 6

Конкретный вопрос. Есть ли способ выяснить, является ли поток ptr или любой ptr по-прежнему ссылаются на действительный объект или памяти?

ps: я мог бы использовать память кучи и делать что-то с умными указателями, но честно говоря, я хотел бы сохранить это, если возможно

Нет, нет стандартного способа проверки, если необработанный указатель или ссылка все еще ссылаются на действительный объект.

RAII - это стандартное решение на С++ для такого типа проблем, поэтому, на мой взгляд, вы должны смотреть на интеллектуальные указатели. Я не знаю ни одной библиотеки, предоставляемой интеллектуальным указателем, который бы разрешил эту конкретную проблему, но решение RAII, основанное на совместной собственности, кажется лучшим решением здесь.