在引用超出范围后如何检测 ptr 是否仍在引用有效引用

How to detect if a ptr is still referencing a valid reference after that reference goes out of scope

我正在研究流,但无法理解以下内容。

这里我们有一个基本的 ostream ptr,它被设置为不同的输出流,无论是 coutcerr 还是 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;
}

上面的代码运行完美,展示了 c++ iostream 实现的强大功能。完美的。

但是,由于 setOutput 是通过引用设置的,因此引用的对象必须保留在范围内。这就是问题出现的地方。如果 ofstream 或任何其他 ostream 无效,我想找出一种将输出默认为 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() returns 到 main 函数。那里出现了可怕的错误,因为本地定义的 ofstream 不再可访问。

显然这是不可取的,用户应该意识到这一点。但是,我想将所有这些都包装在日志记录 class 中,从而使对象的状态保持有效,即使这种滥用可能会发生。它将导致难以发现的无效访问冲突。

具体问题。有什么方法可以确定 ostream ptr 或与此相关的任何 ptr 是否仍在引用有效的对象或内存位置?

ps:我可以使用堆内存并用智能指针做一些事情,但坦率地说,如果可能的话我想保持这样

一种可能的方法是创建一个 RAII class 在将流传递到 setOutput 之前包装流。此 class 应设计为像 shared_ptr 一样工作,以便它维护共享引用计数。 writeData 然后检查它是否具有唯一剩余的引用,如果是,则销毁 ostream 并默认为 cout。

这听起来像是 RAII 的一个很好的用例。

写一个 class ,它接受一个文件名和一个 std::ostream** 作为其构造函数的参数。在上述class的构造函数中,构造ofstream(作为成员),并设置指向ofstream的指针。在析构函数中,还原为 stdout.

然后,将以下函数的前两行替换为新的 class.

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

Concrete question. Is there any way to figure out whether an ostream ptr or any ptr for that matter is still referencing a valid object or memory location?

没有。没有办法用原始指针来解决这个问题。至少在标准 C++ 中不是。

您将需要保证所指向的对象在被指向时一直处于活动状态。

用于提供该保证的常见模式是 RAII,如其他答案中所述。保证指针有效性的另一种方法是使用智能指针而不是原始指针。但是,这些与自动变量不兼容。

只要你能保证指针不被取消引用,就可以继续指向死对象。这通常很难保证,因为如前所述,没有办法测试指向的对象是否存在。

您应该使用 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");
}

Concrete question. Is there any way to figure out whether an ostream ptr or any ptr for that matter is still referencing a valid object or memory location?

ps: I could use heap memory and do something with smart pointers but frankly I'd want to keep it like this if possible

不,没有标准的方法来测试原始指针或引用是否仍在引用有效对象。

RAII 是针对此类问题的标准 C++ 解决方案,因此我认为您应该关注智能指针。我不知道有任何库提供的智能指针可以解决这个特定问题,但基于共享所有权的 RAII 解决方案似乎是这里最好的解决方案。

您可以使用将流作为输入的函数来避免这些复杂情况。

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");

事实上,这个功能更好也更容易维护,因为您不必记住在任何给定时间设置了哪个流。对于大型程序来说,这是一个重要的品质。