从unique_ptr派生时,析构函数会引发异常

时间:2019-05-25 07:06:02

标签: c++ smart-pointers

我试图破解unique_ptr的删除器,但是没有通过正确的值来破坏结构(我们无法覆盖删除器,因为它与正确的值相关联)。因此,我决定尝试从unique_ptr派生,这是我的代码。

using OStreamPtr = std::unique_ptr<std::ostream>;

class MockOStreamPtr : public OStreamPtr {

 public:
  MockOStreamPtr(OStreamPtr&& rhs, MockOutputSystem* sys) : OStreamPtr(std::move(rhs)), sys_(sys) {}
  MockOStreamPtr(std::ostream* p, MockOutputSystem* sys) : OStreamPtr(p), sys_(sys) {}
  ~MockOStreamPtr() {
    std::cout << "~MockOStreamPtr" << std::endl;
    if (sys_) {
      std::cout << get()->good() << std::endl;  // this failed already
      // sys_->manage(*this);
    }
  }

 protected:
  MockOutputSystem* sys_ = nullptr;
};

MSVC在销毁期间访问ostream指针时给了我一个SEH异常,我根本无法理解。

紧凑的测试用例:

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

using namespace std;

using OStreamPtr = std::unique_ptr<std::ostream>;

class MockOutputSystem {

 public:
  template <typename T>
  static OStreamPtr MakeStream(T&& rhs) {
    return std::make_unique<T>(std::move(rhs));
  }
  OStreamPtr fopen(const std::string& file);
};

class MockOStreamPtr : public OStreamPtr {

 public:
  MockOStreamPtr(OStreamPtr&& rhs, MockOutputSystem* sys) : OStreamPtr(std::move(rhs)), sys_(sys) {}
  MockOStreamPtr(std::ostream* p, MockOutputSystem* sys) : OStreamPtr(p), sys_(sys) {}
  ~MockOStreamPtr() {
    std::cout << "~MockOStreamPtr" << std::endl;
    if (sys_) {
      std::cout << get()->good() << std::endl;  // this failed already
      // sys_->manage(*this);
    }
  }

 protected:
  MockOutputSystem* sys_ = nullptr;
};

OStreamPtr MockOutputSystem::fopen(const std::string& file) {
  auto s = std::ostringstream();
  s << file << ":" ;
  return MockOStreamPtr(std::move(MakeStream(std::move(s))), this);
}

int main(void) {
  MockOutputSystem sys;
  OStreamPtr s(sys.fopen("test_file.b"));
  (*s) << "hello world";
  s.release();  // failed here
}

1 个答案:

答案 0 :(得分:1)

您在进行各种策略的组合都是不可取的。首先要解决的是派生自未设计为基类的类,请不要这样做。问问自己:MockOStreamPtr是应该成为指向ostream的指针还是应该拥有指向ostream的指针? ?前者建议继承,但也建议不要引入与指针无关的成员(如sys_)。后者更接近您想要的东西:一个使ostreamMockOutputSystem协同工作的类。

选项A

使指针成为类成员,将输出流置于与输出系统相同的级别。

class MockOStreamPtr {
    // Stuff here. I'll leave the details to you.

    protected: // Sure you don't want "private" here?
        OStreamPtr  stream_;
        MockOutputSystem* sys_ = nullptr;
};

这至少具有更好的组织性,但是由于您现在有两个要跟踪的指针,因此它可能更加麻烦。另外,您可能会在析构函数中遇到相同的问题。除非在为release()编写包装程序时发现问题。如果选择选项B,则可能会简化项目。

选项B

您可以从std::ostream派生,而不是从指针派生。这是可以的,因为ostream被设计为继承树的一部分。另一方面,它被设计为该树中的非叶子,因此这种特定的继承不会那么有用。为了获得有用的继承,您可能想从ostream派生出来的东西,或者也许从std::ofstream派生出来。

如果ofstream是唯一需要使用输出系统知识进行扩展的类,则可以很简单地解决。如果您需要扩展其他类,模板可以几乎一样简单地工作。

class MockOStream : public std::ofstream {
    // Stuff here. I'll leave the details to you.

    protected: // Sure you don't want "private" here?
        MockOutputSystem* sys_ = nullptr; // Maybe you want a reference instead?
};

// *** OR  ***

template <class S>
class Mock : public S {
    // Stuff here. I'll leave the details to you. Write code as if S is ostream.

    protected: // Sure you don't want "private" here?
        MockOutputSystem* sys_ = nullptr; // Maybe you want a reference instead?
};

一个全面,强大的输出系统应该为您提供这种包装,以使您不必担心此类细节。您只需担心要避免使用object slicing。但是,如果MockOutputSystem::fopen必须返回ofstream而不是MockOStream,则可以将包装器注入构造过程。

int main(void) {
    MockOutputSystem sys;
    // The next line is awkward; don't use it as written.
    // The point is that a MockOStream's address can be used as a pointer-to-ostream.
    std::unique_ptr<std::ostream> s = std::make_unique<MockOStream>(sys.fopen("test_file.b"));
    s << "hello world";
    s.release();  // <-- destroy the stream, using the virtual destructor
}

这更好地封装了您想要的内容,不是吗?当流被销毁时,您希望基于关联的输出系统发生某些事情。不是在 pointer 被破坏时,而是在 stream 被破坏时。

这导致您的代码崩溃的具体原因。当get()->good()为空时,对get()的调用是内存冲突。在您的示例程序中,get()将为空,因为在指针的析构函数之前调用了release()。对release()的调用将破坏指向的对象(流)并将存储的指针设置为null。到指针的析构函数被调用时,对该流执行任何操作已经为时已晚。