【问题标题】:Why the constructor of std::ostream is protected?为什么 std::ostream 的构造函数受到保护?
【发布时间】:2013-08-04 13:52:09
【问题描述】:

为什么我不能像这样为我的输出创建一个“空”流

std::ostream out;

?

对于clang 3.4gcc 4.8.1 在Linux 下使用libstdc++,这些行显然是非法的,我真的不明白为什么,我的意思是为什么我不能随便创建一个流并像这样使用它我想要 ?请注意,std::ofstream out; 是 100% 可以的。我只是不明白这背后的逻辑。 如果您认为在创建之后我可以使用此缓冲区并与 copyfmt 的其他缓冲区共享一个公共缓冲区,那就更奇怪了,所以我的 std::ostream 没有真正需要从创建开始就被初始化为正确的东西有用的对象。

请不要偏离这一点,我不需要stringstreams,我需要ios 流,因为我必须做的事情以及它们提供的方法和属性。

【问题讨论】:

  • 你希望ostream 做什么?
  • @Rapptz 的行为类似于 std::coutstd::cerr ?就像输出流一样?
  • 我认为他的意思是“输出到哪里”。 BTW - cout 和 cerr 输出到不同的位置。它们恰好是默认打印到屏幕上的位置
  • @cluracan 这个位置是什么?我只是不在乎位置,我需要一个通用输出流
  • cerr 打印到“标准错误流”。该流可以被截获并移动到例如日志文件(即使在编译之后 - 您可以在运行程序时执行此操作)。 cout 打印到“标准输出流”。这也可以被截获并移动到文件中,或者作为另一个程序的输入等。如果要写入标准输出,请使用 cout。如果要写入标准错误流,请使用 cerr。如果你想在其他地方写,你首先需要确定“其他地方”在哪里。 tl;dr - 使用 cout。

标签: c++ iostream ostream


【解决方案1】:

我承认我也不明白。我找不到任何 std::istream 的默认构造函数,我认为 如果你想创建一个双向流,你会想要一个, 因为std::ios_base 的奇怪方式工作: 构造函数初始化任何东西,但派生的 类必须在其中显式调用std::ios_base::init 构造函数。当涉及多重继承时(即 双向 IO,其中类派生自两者 std::istreamstd::ostream),我只希望最多 派生类调用std::ios_base::init。 (在 std::iostreamstd::ios_base::init 将被调用两次。) 事实上,在标准中查找之前,我正要 回答默认构造函数受到保护,因为它 没有调用std::ios_base::init,而是直接使用 比在派生类中,会导致未初始化 流。

不管怎样,你眼前的问题有一个简单的解决方案:

std::ostream out( NULL );

另外:您稍后需要设置其接收器的功能是 rdbuf() 的非 const 版本,而不是 copyfmt()rdbuf() 是 用于读取和设置指向streambuf 的指针, copyfmt() 复制格式标志,但 触摸 指向streambuf 的指针。

所以你可以这样做:

std::ostream out( NULL );
//  ...
std::filebuf fileBuffer;
if ( filenameGiven ) {
    fileBuffer.open( filename.c_str(), std::ios_base::out );
}
if ( fileIsOpen() ) {
    out.rdbuf( &fileBuffer );
} else {
    out.rdbuf( std::cout.rdbuf() );
}

(我经常这样做。其实我以为是平常的 事先不知道是否输出到文件时的习惯用法 或std::cout。)

编辑:

还有一个更正:rdbuf 的非常量版本调用clear(), 所以你不必。 (我知道我没有打电话给clear()就做到了,但是 当我看到init 设置badbit...)

无论如何:总结是:通常最好将指针传递给有效的 streambuf 到std::ostream 的构造函数,但如果你不能,那就是 传递一个空指针是完全有效的,稍后使用 rdbuf()。否则的答案是完全错误的。

【讨论】:

  • 终于:D。标准库的 ios 部分对程序员来说是一段真正的旅程......
  • 有趣的初始化舞蹈发生在std::iosstd::istream/std::ostream/std::iostream之间:除非他们想直接从std::ios派生,否则用户不必处理它!完成舞蹈的原因是避免在构造std::iostream 时设置两次流缓冲区:std::ios::init() 只是从该类的默认构造函数中调用。 ...这实际上也是std::ostream 的默认构造受到保护的原因:它不会触及std::ios 中的流缓冲区并使其未初始化!
  • @user2485710 它实际上比标准库的其他部分要简单得多。从用户的角度来看,这当然很复杂,因为它正在做一项非常复杂的工作。
  • @DietmarKühl 这就是我最初的想法。我在 C++11 中找不到 std::istreamany 默认 ctor; std::iostream 的构造函数被指定为使用 streambuf 指针调用 std::istreamstd::ostream(这导致 std::ios::init() 被调用两次)。难道我们都在考虑经典的 iostream 吗?
  • @JamesKanze:你是对的。 ...而且 C++2003 中也没有默认构造函数(我没有 C++1998 的版本可以在那里检查)。这很奇怪,虽然不应该有太多问题,因为流缓冲区最终需要初始化,而且似乎只是通过std::istream 分支来完成。
【解决方案2】:

std::ostream 在概念上是抽象的 - 你不应该创建它们(但有关更多详细信息,请参阅其他答案)。

std::stringstream 为您提供std::ostream 所做的一切,因为它派生自它。这也意味着在任何你想要std::ostream& 的地方,例如在函数参数中,你都可以传递一个std::stringstream 对象。

您说“没有必要对 [it] 进行初始化 [...] 才有用”。这根本没有意义。您不能使用未初始化的任何东西:即使是ints。

【讨论】:

  • 我忘了说明,从我的角度来看,对用户给定的特定值或状态的初始化可能是无用的,这就是我所说的意思;但无论如何,我在哪里可以找到 ostream 的层次结构?
  • std::ostream 一点都不抽象!它甚至没有任何virtual 函数,除了它的析构函数。您可以使用带有std::streambuf* 的构造函数来创建std::ostream
  • 好吧,概念上是抽象的,而不是明确抽象的。我认为采用 streambuf 的 CTR 是工厂.... :)
  • @SteveL 你说错了。使用std::ostream 的实例是完全正确的,并且实际上非常频繁(至少在我的工作中)。事实上,派生自std::ostream 的类只是为了方便。基本上,您唯一需要的课程是std::ostream。 (而且我不明白你在哪里看到工厂。std::ostream 的构造函数永远不会调用new。)
【解决方案3】:

std::basic_ostream默认 构造函数是 protected,因为在不设置 std::basic_streambuf 的情况下创建 std::basic_ostream 通常没有任何意义,而默认构造函数实际上没有做任何初始化(见下文)。但是,还有另一个构造函数将流缓冲区作为参数。如果你真的需要一个std::ostream,它还没有设置做任何事情,你可以使用那个构造函数:

std::ostream out(0);

std::ostream 将设置std::ios_base::badbit,直到使用std::ios::rdbuf() 设置流缓冲区。

std::ostream 的默认构造函数是protected 的根本原因是它实际上故意不做任何有用的事情!特别是,[从进一步的派生类]调用此构造函数将不会初始化流缓冲区:标准不明确规定任何行为,即默认构造函数隐含的行为生成默认构造函数(或者,在 C++2011 中,就好像它是使用 = default 定义的一样)。要初始化流缓冲区,它需要调用std::ios::init()。这种奇怪行为的原因是构造进一步派生类std::iostream 的对象将初始化std::ios 对象两次,一次通过std::ostream,一次通过std::istream

与其他一些答案所暗示的不同,std::ostream 根本不是抽象的。事实上,它只有一个 virtual 函数,那就是它的析构函数(析构函数是 virtual 恰好是我的错;我不完全相信强制这样做真的是个好主意)。

【讨论】:

  • 我认为你措辞错误。说“默认构造函数是abstract”是什么意思?函数不是抽象的,类是抽象的,构造函数也不能是虚拟的。默认构造函数根本不存在(因为还有其他构造函数,编译器也不会生成它)。
  • @JamesKanze:好点。我的意思是写 protected (稍后会更正)。谢谢!
  • 你的帖子正是我的想法,直到我在标准中查找它。 is 没有默认构造函数,至少我能找到。你所描述的比标准中的更有意义(这也是我的想法,直到我查到它),但似乎并非如此。 (也许经典的、预标准的 IO 流就是这种情况,我们都在考虑这个问题。)
  • @JamesKanze:很尴尬,但你是对的!甚至没有protected 默认构造函数。这意味着标准库将有一个用户无法使用的额外构造函数(即,它采用用户无法命名的类型)并且不执行任何操作。关于 IOStreams 仍然需要学习(或更可能重新学习)的东西! ;)
  • 也感谢您的贡献,阅读详细信息总是很高兴。我可能很快就会加入 comp.lang.c++ :D
【解决方案4】:

stringstream 是一个ios 流。

但是关于你的问题:

ostream 在某处写信。如果你声明

std::ostream out;
out << "Hello, world!"<<std::endl

"Hello, world!" 应该写在某处。但是哪里?这取决于每个特定ostream 的实现。是的,有一个缓冲区,但是那个缓冲区也取决于具体的实现。

所以当你说“我想要一个ostream”时,我不得不问 - 一个将东西写...到哪里?

根据您的回答,这将告诉我您需要使用 ostream 的哪个具体实例/实现。

【讨论】:

  • 好吧,如果我写 int a; 我不必声明 int 的存储位置,编译器为我做了,为什么流不能有“默认”“回退”值并且仍然处于有效状态?
  • 因为它必须在某处打印!它会在哪里打印?如果有默认值,那就是“我不知道打印到哪里,所以我会扔掉你写的所有东西”。这相当于您的 int 示例(如果您只能写入 int 而不能从中读取,例如 ostream)。但这种行为很愚蠢,很可能不是你想要的。告诉我们你在写东西时想要实际发生什么,以及为什么 cout 不够好
  • 场景:我有一个用 C++ 编写的程序,它还提供脚本语言的包装器,我需要存储和输出(基本上有一个缓冲区)关于应用程序的状态、错误和调试消息,但我想为 C++ 应用程序和脚本包装器分开这 3 个缓冲区,所以我总共需要 6 个缓冲区,我不希望它们混合在一起,我希望它们永远不会看到另一个我想决定我可以重定向这 6 个流的输出:你选择什么类或类型?
  • 然后让您的班级 (?) 接收并记住指向 ostream 的指针(或引用),它将输出任何内容。然后在创建此类时,您可以决定是否要给它cout(输出到“标准输出”,或ostringstream 稍后在内部使用,或ofstream 写入文件。你一直没有说你想要输出到哪里!如果你想要屏幕,你使用cout。如果你想要屏幕,但也想“让它们分开”,那么解释一下这是什么意思......
  • 如果我创建一个stringstream 我不必做出这样的选择,为什么库的这一部分很难创建一个对象,让对象随着内容被插入到流中,让用户在代码中的某个点决定在哪里输出内容;为什么我需要预先设置这种东西?顺便说一句,我可能的选择是cout,cerr,clog 和一个文件。
【解决方案5】:

std::ostream 是一个通用的流类。正因为如此,它无法知道它正在流向什么,这就是拥有一个通用类的全部意义所在。

当您将数据发送到流时,它实际上并不存储数据本身。它只是将其转发给关联的缓冲区。考虑到这一点,创建一个通用流而不分配这个缓冲区是没有意义的,因为通用类不能创建一个空的通用缓冲区。 (缓冲区本身需要是一个具体类型的缓冲区,这也可以通过查看根本没有公共构造函数的广义缓冲区 std::treambuf 来理解)

将此与 std::ofstream 进行比较,后者是具有特定类型缓冲区的流,ofstream 现在可以知道它使用哪种类型的缓冲区,从而能够实例化默认的 std::filebuf .

解决您的具体问题。

首先创建所需类型的缓冲区,然后以缓冲区为参数创建一个通用的 std::ostream。然后您可以稍后使用 std::filebuf::open() 连接到文件。

例子:

std::filebuf fileBuffer;
std::ostream myOstream(&fileBuffer); // Hand over the address of the fileBuffer

fileBuffer.open("filename.txt", std::ios::out);
myOstream << "Text to file";

【讨论】:

  • 他的重点是直到后来他才拥有streambuf,所以他不能这样做。这可能是一个有问题的架构; streambuf 通常需要至少与 istream 一样长的生命周期,所以人们会期望它首先被创建,但这条规则肯定有例外。
猜你喜欢
  • 1970-01-01
  • 2017-12-23
  • 2010-11-06
  • 2016-04-07
  • 2019-11-18
  • 2020-09-17
  • 1970-01-01
  • 2011-05-30
  • 1970-01-01
相关资源
最近更新 更多