我试图破解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
}
答案 0 :(得分:1)
您在进行各种策略的组合都是不可取的。首先要解决的是派生自未设计为基类的类,请不要这样做。问问自己:MockOStreamPtr
是应该成为指向ostream
的指针还是应该拥有指向ostream
的指针? ?前者建议继承,但也建议不要引入与指针无关的成员(如sys_
)。后者更接近您想要的东西:一个使ostream
和MockOutputSystem
协同工作的类。
选项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。到指针的析构函数被调用时,对该流执行任何操作已经为时已晚。