【问题标题】:Bind temporary to non-const reference将临时绑定到非常量引用
【发布时间】:2012-02-25 00:51:09
【问题描述】:

基本原理

我尽量避免在 C++ 代码中进行赋值完全。也就是说,我只使用初始化并尽可能将局部变量声明为const(即,始终除了循环变量或累加器)。

现在,我发现了一个不起作用的案例。我相信这是一种普遍模式,但特别是在以下情况下会出现:

问题描述

假设我有一个程序将输入文件的内容加载到字符串中。您可以通过提供文件名 (tool filename) 或使用标准输入流 (cat filename | tool) 来调用该工具。现在,如何初始化字符串?

以下操作无效:

bool const use_stdin = argc == 1;
std::string const input = slurp(use_stdin ? static_cast<std::istream&>(std::cin)
                                          : std::ifstream(argv[1]));

为什么这不起作用?因为slurp的原型需要如下图:

std::string slurp(std::istream&);

也就是说,我的参数 non-const 因此我不能将它绑定到一个临时的。似乎也没有办法使用单独的变量来解决这个问题。

丑陋的解决方法

目前,我使用以下解决方案:

std::string input;
if (use_stdin)
    input = slurp(std::cin);
else {
    std::ifstream in(argv[1]);
    input = slurp(in);
}

但这让我很不爽。首先,它有更多代码(在 SLOC 中),但它也使用 if 而不是(这里)更合乎逻辑的条件表达式,并且它使用声明后的赋值,这是我想避免的。

有没有避免这种间接初始化方式的好方法?这个问题很可能会泛化到需要对临时对象进行变异的所有情况。流是不是设计不当以应对这种情况(const 流没有意义,但处理临时流确实有意义)?

【问题讨论】:

  • 这里为什么需要static_cast
  • @n.m.:编译器无法看穿?:: 两边的类型必须相同。
  • “流的设计不是很糟糕吗?”是的,非常如此。
  • @VJovic 我与这个问题并不真正相关,但它只是读取直到它到达流的末尾,并将结果存储在一个连续的字符串中。
  • 我想主要的问题是 C++ 没有考虑到这种风格。在 Haskell 工具中,当传递文件名时,我通过递归函数将 stdin 替换为文件流,但我认为这在这里不合适。

标签: c++ constants temporary


【解决方案1】:

为什么不简单地重载slurp

std::string slurp(char const* filename) {
  std::ifstream in(filename);
  return slurp(in);
}

int main(int argc, char* argv[]) {
  bool const use_stdin = argc == 1;
  std::string const input = use_stdin ? slurp(std::cin) : slurp(argv[1]);
}

这是一个带有条件运算符的通用解决方案。

【讨论】:

  • +1 一个很好的解决方案。目前我在 Python 中经常使用它,但奇怪的是,我没有想到在 C++ 中做同样的事情。
【解决方案2】:

if 的解决方案或多或少是标准解决方案,当 处理argv

if ( argc == 1 ) {
    process( std::cin );
} else {
    for ( int i = 1; i != argc; ++ i ) {
        std::ifstream in( argv[i] );
        if ( in.is_open() ) {
            process( in );
        } else {
            std::cerr << "cannot open " << argv[i] << std::endl;
    }
}

但是,这不能处理您的情况,因为您主要关心的是 获取一个字符串,而不是“处理”文件名 args。

在我自己的代码中,我使用了我编写的MultiFileInputStream,它 在构造函数中获取文件名列表,并且仅在以下情况下返回 EOF 最后一个已被读取:如果列表为空,则读取为std::cin。这 为您的问题提供优雅而简单的解决方案:

MultiFileInputStream in(
        std::vector<std::string>( argv + 1, argv + argc ) );
std::string const input = slurp( in );

这个类值得写,因为如果你经常 编写类 Unix 实用程序。然而,这绝对不是微不足道的, 如果这是一次性需要,可能需要做很多工作。

更通用的解决方案是基于您可以调用 非常量成员函数就一个临时的,事实上大部分 std::istream 的成员函数返回 std::istream&amp;—a 然后将绑定到非 const 引用的非 const-reference。所以 你总是可以这样写:

std::string const input = slurp(
            use_stdin
            ? std::cin.ignore( 0 )
            : std::ifstream( argv[1] ).ignore( 0 ) );

不过,我认为这有点像 hack,而且它更通用 无法检查是否打开的问题(由构造函数调用 的std::ifstream 工作。

更一般地说,虽然我了解您想要实现的目标,但我 想你会发现 IO 几乎总是代表一个异常。 你不能在没有先定义的情况下阅读int,你也不能 在没有先定义std::string 的情况下读取一行。我同意 它没有它可以的那么优雅,但是,正确的代码 处理错误很少像人们希望的那样优雅。 (一种解决方案 这里将派生自std::ifstream 以抛出异常,如果 打开没有工作;你所需要的只是一个检查过的构造函数 is_open() 在构造函数体中。)

【讨论】:

  • +1 我最喜欢MultiFileInputStream 解决方案。如果流 API 不能解决您的问题,请在顶部添加一个 shim。 :)
  • 流 API 确实解决了这个问题。您只需要扩展实现。 (MultiFileInputStream 继承自 std::istream。iostreams 的设计考虑到了扩展,我想不出我们没有至少一个自定义 streambuf 和任意数量的自定义操纵器的应用程序。)跨度>
【解决方案3】:

所有 SSA 风格的语言都需要有 phi 节点才能实际使用。在任何需要根据条件值从两种不同类型构造的情况下,您都会遇到同样的问题。三元运算符不能处理这种情况。当然,在 C++11 中还有其他技巧,例如移动流等,或使用 lambda,而 IOstreams 的设计实际上与您尝试做的完全相反,所以在我看来,您只需要破例。

【讨论】:

  • 谢谢,我不知道这个通用问题的名称(显然我需要重新阅读龙书)。 iostreams 的 phi 函数确实是我需要的,移动可能是一个合适的解决方案。太棒了,学到了一些有趣的东西。
【解决方案4】:

另一个选项可能是保存流的中间变量:

std::istream&& is = argc==1? std::move(cin) : std::ifstream(argv[1]);
std::string const input = slurp(is);

利用命名右值引用是左值这一事实。

【讨论】:

  • 我无法理解为什么这是合法的。这如何确保 ifstream 的析构函数在作用域结束时被调用?
  • @Konrad:在绑定临时对象时,您熟悉引用到常量的规则吗?他们在这里申请。临时ifstream 对象的生命周期被延长,就像它绑定到std::istream const&amp; 一样,并且当引用超出范围时调用析构函数。在这里使用右值 ref 的好处是,您可以随意修改对象。
  • 我对此很熟悉。但是,如果这适用于这里,那么如果 std::cin 的析构函数在您的代码中绑定到 is,它不会被调用两次吗?我的意思是,通常它不会,但条件表达式的类型到底是什么,它如何影响编译器决定是否将对象的生命周期绑定到is'范围?
  • 被销毁的对象不一样。一个是原来的X("a"),另一个是新创建的对象,是移动操作的目标。请参阅ideone.com/s9L62 以获得更完整的图片。
  • @vhallac:谢谢,这是有道理的。不过太糟糕了,因为这个答案现在真的没用了……谁想要一个从标准输入移过来的? :(
猜你喜欢
  • 1970-01-01
  • 2013-04-29
  • 2022-01-01
相关资源
最近更新 更多