包装后的std :: istream,unique_ptr麻烦包装不为null

时间:2019-05-20 07:03:13

标签: c++ c++11 memory iostream smart-pointers

由于无法移动std :: istream(受保护的功能),因此我尝试包装std :: istream,以便可以在自定义流工厂上构建代码。

到目前为止,我已经尝试像这样直接从std :: istream继承:

class IStream : public std::istream{
public:
  // template <typename T>
  IStream(std::istringstream&& rhs) : std::istream(std::move(rhs)){
    this->rdbuf(rhs.rdbuf());
    rhs.istream::rdbuf(NULL);
  }
  IStream(IStream&& rhs) : std::istream(std::move(rhs)){
    this->rdbuf(rhs.rdbuf());
    rhs.rdbuf(NULL);
  }
};

但是这会导致分段错误(对此原因的任何见解都会受到赞赏),因此我继续使用一些“更安全的外观”方法。

这是我当前使用的代码:

#include <iostream>
#include <sstream>
#include <istream>
#include <string>
#include <memory>
#include <cassert>

using namespace std;

class IStream {
 public:
    IStream(const IStream& rhs) = delete;
  template <typename T>
  IStream(T& rhs) : shared_(rhs) {
    std::cout << "called IStream(T&)" << std::endl;
  }
  // assume strict order between member construct for now
  template <typename T>
  IStream(T&& rhs) : owned_{std::make_unique<T>(std::move(rhs))}, shared_(*owned_) {
    std::cout << "called IStream(T&&)" << std::endl;
  }
  IStream(IStream&& rhs) : owned_(std::move(rhs.owned_)), shared_(*owned_) {
    assert(rhs.owned_.get() == nullptr); // failed
    std::cout << "called IStream(IStream&&)" << std::endl;
  }
  std::istream& get() {
    return shared_;
  }
  ~IStream() {
    std::cout << "called ~IStream with " << (owned_.get()!=nullptr) << std::endl;
  }
 private:
  std::unique_ptr<std::istream> owned_;
  std::istream& shared_;
};

IStream&& wrap() {
    return IStream(istringstream{"test"});
}

int main(void) {

    IStream is(wrap());
    char buf[10];
    memset(buf, 0, sizeof(char) * 10);
    is.get().getline(buf, 10);
    std::cout << std::string(buf) << std::endl; 

    return 0;
}

可悲的是,该代码仍然无法正常工作,我发现在IStream::IStream(IStream&&)的声明失败了。

输出:

called IStream(T&&)
called ~IStream with 1
Assertion failed: rhs.owned_.get() == nullptr, file .\tmp.cpp, line 23

这会导致更奇怪的现象,即移动后unique_ptr不为null。

我正在使用MSVC编译器。

2 个答案:

答案 0 :(得分:3)

你不能理智地做你想做的事。

据我了解,您想要某种与::std::istream尽可能不可区分的对象,但具有移动语义,以便在不再需要它时将其自动销毁。

任何可以称为::std::istream的东西的主要有用属性是它是从::std::istream派生的,并且由于它是::std::istream不能被移动,所以什么也没有从它派生的也可以移动。

因此,您剩下的第二件事是,::std::unique_ptr::std::istream好。这意味着您必须一直使用*,而且很难看。哦,很好。

没有什么聪明的方法可让您创建可同时移动并从{{​​1}}派生的任何类型的包装器,以便它可以与所有期望的现有库函数一起使用。您也无法做出以任何合理方式行事的事物,例如参考文献,而实际上没有作为参考文献。

顺便说一句,构造::std::istream时,如果您实际上要指向不想删除的unique_ptr之类的对象,则可以提供自定义删除器。在这种情况下,您可以使自定义删除器完全不执行任何操作。当您移动::std::cin时,自定义删除器将随指针一起向右移动。

答案 1 :(得分:2)

此函数返回一个悬空的引用:

IStream&& wrap() {
    return IStream(istringstream{"test"});
}

函数返回时,临时对象将被销毁。而是将函数更改为按值IStream wrap() {返回。

此外,shared_(*owned_)导致悬空引用,因为它引用了当前拥有的对象所在的内存位置,即使该对象被销毁并且后来将unique_ptr更改为拥有另一个对象也是如此。

摆脱shared_,并根据需要调用owned_.get()是个好主意。

相关问题