如何以允许传递临时变量的方式将 std::istream 传递给函数?

How can you pass an std::istream into a function in a way that allows to pass temporaries?

我正在尝试创建一个构造函数来从给定的任何 istream 加载资源。我似乎无法找出将 istream 参数传递给构造函数的最佳方法。

Loader::Loader(istream stream);

由于对象切片,这个显然很糟糕,所以别无选择。

Loader::Loader(istream& stream);

这就是我现在使用的,看起来还不错。但是它有一个重要的问题——你不能给它一个临时的,因为临时的不能绑定到非常量引用!例如,以下将不起作用:

Container():
  mLoader(ifstream("path/file.txt", ios::binary)
{
}

这是一个相当大的限制,因为我现在被迫将 ifstream 存储为 Container 的成员变量,以延长其生命周期。

由于问题出在非 const 引用上,因此可以这样想:

Loader::Loader(const istream& stream);

但是由于 .seek() 等是非常量,这也不是一个选项...

那么,如何巧妙的解决这个问题呢?

例子

Container():
  mLoader(ifstream("path/file.txt", ios::binary)
{
}

… 构思不当,因为临时对象的生命周期只是构造函数调用。然后你会留下一个 悬空引用 。使用该引用将是未定义的行为。

但是,从技术上讲,为了能够将临时参数传递给非 const 形式参数的引用,只需像这样定义一个小助手:

template< class Type >
auto temp_ref( Type&& o )
    -> Type&
{ return o; }

然后你可以打电话给foo( temp_ref( Whatever() ) ),但请记住,该临时对象的生命周期是它出现的完整表达式。

如果您的编译器是 c++11 或更高版本,您可以简单地提供一个将 istream 作为右值引用的构造函数版本:

void Loader::Loader(std::istream&& is)

快速示例:

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>

struct Loader
{
    Loader(std::istream& is)
    {
        read(is);
    }

    Loader(std::istream&& is)
    {
        read(is);
    }

    void read(std::istream& is)
    {
        is >> std::quoted(x);
        is >> std::quoted(y);
    }

    std::string x, y;
};

std::ostream& operator<<(std::ostream& os, const Loader& l)
{
    os << "x = " << l.x;
    os << ", y = " << l.y;
    return os;
}


auto main() -> int
{
    using namespace std;

    Loader l(istringstream(R"text("donkey" "horse")text"));
    cout << l << endl;

    istringstream not_temp(R"text("apple" "banana")text");
    Loader l2(not_temp);
    cout << l2 << endl;

    return 0;
}

预期输出:

x = donkey, y = horse
x = apple, y = banana

将指针传递给新创建的对象是可行的。

Loader(istream * pstream)
{
    try
    {
        // ......

        delete pstream;
        throw; 
    }
    catch(...)
    {    
        delete pstream;                  
    }
}

Container():
mLoader(new ifstream("path/file.txt", ios::binary))
{
}