提问人:Markus Moll 提问时间:5/30/2023 最后编辑:Markus Moll 更新时间:5/30/2023 访问量:76
类设计:如何实现 Log-Listener 类的 RAII 资源管理
Class design: How to implement RAII ressource management for Log-Listener class
问:
编辑:好吧,这个问题似乎不适合该平台,因为这里没有真正的技术背景。没问题(真的,没有讽刺),我会在其他地方寻求建议。无论如何,谢谢。
我有一个纯粹关于类设计的问题:假设,我们想将 LogListener 对象附加到中央管理主机(在最小示例中为 Host 类。我们无法更改此实现,因为它来自外部代码库)。
要确保的一件关键事情是,一旦我们的聆听任务完成,就要与主机分离。为此,我想从代码库中某个类的析构函数中调用主机的分离方法(此任务的当前选项是简化类 Manager{A,B,C})。
我现在的问题是如何设计它。我列出了三个选项,我目前选择第一个选项,因为它目前使用起来最方便,但它违反了单一责任原则(管理主机连接和侦听日志)。进一步的侦听器派生不会从连接管理中受益。
你对走哪条路有什么建议吗?我很确定,我错过了选项 4、5、......如果您有进一步的想法,非常欢迎!
#include <iostream>
#include <memory>
#include <set>
#include <utility>
// Simplified base class for the Listener interface
// ================================================
class Listener {
public:
virtual void listen(const std::string& msg) = 0;
};
// Simplified implementation of a Host, storing logging listeners
// ==============================================================
class Host {
public:
void attach(Listener* listener) { m_coll.insert(listener); };
void detach(Listener* listener) { m_coll.erase(listener); };
void call(const std::string& msg) { for(auto* L : m_coll) { L->listen(msg); }}
private:
std::set<Listener*> m_coll;
};
static Host globalHost{};
class DerivedListener : public Listener {
public:
void listen (const std::string& msg) override { std::cout << "Two!\n"; }
};
// Solution 1. Violates single responsibility, but has no "lifetime issues"
// Manager + Listener in one class
// ========================================================================
class ManagerA : public Listener {
public:
ManagerA() { globalHost.attach(this); }
~ManagerA() { globalHost.detach(this); }
void listen (const std::string& msg) override { std::cout << "One!\n"; }
};
// Solution 2. Has lifetime issues. Takes a raw-pointer to the listener but
// what if the managed pointer is deallocated during lifetime of the Manager?
// Moreover it has Optional semantics - the pointer may be NULL but then it
// cannot be attached as Host does not tolerate nullptrs.
// ==========================================================================
class ManagerB {
public:
explicit ManagerB (Listener* listener) : m_ptr{listener} { if (m_ptr) globalHost.attach(m_ptr); }
~ManagerB() { globalHost.detach(m_ptr); }
private:
Listener* m_ptr;
};
// Solution 3: Takes ownership of the managed ressource -> No lifetime issues
// but must implement Optional-Semantics because the internal ressource is
// empty after the claim. The claim again is neccessary as the caller needs
// access to the managed object and make independent of the manager.
class ManagerC {
public:
explicit ManagerC (std::unique_ptr<Listener> listener) : m_ptr{std::move(listener)}
{ if (m_ptr) globalHost.attach(m_ptr.get()); }
~ManagerC() { globalHost.detach(m_ptr.get()); }
std::unique_ptr<Listener> claim() { globalHost.detach(m_ptr.get()); return std::exchange(m_ptr, nullptr); }
private:
std::unique_ptr<Listener> m_ptr;
};
int main() {
ManagerA mA{};
DerivedListener listenerB;
ManagerB mB{&listenerB};
ManagerC mC{std::make_unique<DerivedListener>()};
auto listenerC = mC.claim();
return 0;
}
答: 暂无答案
评论
template<class T> class Manager { private: T listener; };
claim
if (m_ptr)
host::call
listen