类设计:如何实现 Log-Listener 类的 RAII 资源管理

Class design: How to implement RAII ressource management for Log-Listener class

提问人:Markus Moll 提问时间:5/30/2023 最后编辑:Markus Moll 更新时间:5/30/2023 访问量:76

问:

编辑:好吧,这个问题似乎不适合该平台,因为这里没有真正的技术背景。没问题(真的,没有讽刺),我会在其他地方寻求建议。无论如何,谢谢。

我有一个纯粹关于类设计的问题:假设,我们想将 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;
}
C++ 类设计 RAII

评论

5赞 Sam Varshavchik 5/30/2023
有几种方法可能。这真的是在问哪一个是最好的;然而,这将是一个意见问题,因为每个人对“最好”的含义都有自己的定义,征求意见对 Stackoverflow 来说不是一个合适的问题。你能重新构建你的问题,而不是“建议”,而是提出一个具体的、技术性的问题,可以用事实或引文来回答吗?
0赞 apple apple 5/30/2023
不确定你想要什么,但 IMO 看起来你只需要一个获取数据的函数?
0赞 Ted Lyngmo 5/30/2023
4:template<class T> class Manager { private: T listener; };
0赞 ruakh 5/30/2023
我不明白为什么你说选项 #2 和 #3 需要支持指针为/变为 null,而选项 #1 不支持类似的东西。为什么不直接使用选项 #3 而不使用函数和 ?claimif (m_ptr)
0赞 Red.Wave 5/30/2023
的调用在哪里?如果特定听众被编辑,您如何收到通知?!是否有任何并行化/并发性或只是线性执行?!虽然这个问题很长,但它并没有提供正确答案的必要细节。host::calllisten

答: 暂无答案