如何跨库共享 C++ 中的对象

How to share objects in c++ across libraries

假设我有这样一个程序:

文件main.cpp

#include "something.hpp"

int main(int argc, char* argv[]) {
    some = new Something();
    return 0;
}

它将链接到由以下文件组成的 .so 库:

文件logger.hpp

#include <iostream>

class Logger {
    public:
        Logger();
        void log(char);
        void set_name(char);
    private:
        char m_name;
};

文件logger.cpp

#include "logger.hpp"

Logger::Logger() {}
void Logger::log(char msg) {
     std::cout << this->m_name << " : " << msg;
}
void Logger::set_name(char name) {
    this->m_name = name;
}

文件something.hpp

#include "logger.hpp"

class Something {
    public:
        Something();
};

文件something.cpp

#include "something.hpp"

Something::Something() {
    logger->log("hello !");
}

现在的代码将在 something.cpp at logger->log() 中失败,因为 logger 从未被定义过。我可以通过添加 logger = new Logger() 来解决这个问题。但我只想创建一个新的 Logger 实例,如果 none 已在使用该库的程序/库中创建。当已经创建一个实例时,我可以通过添加 extern Logger logger; 来使用它。但是当创建了 no 实例时,这将不起作用。有什么建议吗(有可能吗?)?

注意:我已经在使用 Gtkmm4 / Glibmm2.6,也许有使用 Gtk 或 Glib 的解决方案...

第一种方法:单例

如评论中所述,您可以使用 Singleton design pattern 实现这一目标。但是,请记住此模式有 several drawbacks, 其中两个是:

  • 单例允许全局访问。
  • 单例很难进行单元测试。

编写高质量软件时哪些是真正的问题。另外,对于您的特定 情况下,请务必阅读 this answer,其中解释了如何确保一切 链接适当,这样你就不会得到你的多个实例 单例。

第二种方法:依赖注入

我决定在这里 post 一个答案来说明另一种做事的方式 解决了上面提到的两个问题:dependency injection (DI)。与迪, 您不创建依赖项,而是通过参数注入它们。为了 例如,而不是:

Something::Something() {
    auto logger = new Logger(); // Dependency creation (not injection)
    logger->log("hello !");
}

你会得到类似的东西:

Something::Something(Logger* p_logger) { // Logger dependency injected through p_logger
    p_logger->log("hello !");
}

注意DI本身并不能解决“一个实例”的问题。必须注意 创建一次依赖项(通常在 main 中),然后将它们作为 使用它们的参数。但是,全局访问问题已解决。

您可以通过抽象您的依赖关系将其提升到另一个层次。例如, 你可以为你的 Logger class 编写一个接口并使用它:

// Somewhere in your library:
class ILogger
{
public:
    virtual ~ILogger() = default;
    virtual void log(const std::string& p_message) = 0;
    virtual void set_name(const std::string& p_name) = 0;
};

// In Logger.hpp:
class Logger : public ILogger {
    public:
        Logger();

        void log(const std::string& p_message) override;
        void set_name(const std::string& p_name) override;

    private:
        std::string m_name;
};

// In something.hpp/cpp:
Something::Something(ILogger* p_logger) { // Logger dependency injected through p_logger
    p_logger->log("hello !");
}

要实现此目标,您的 main 可能如下所示:

int main(int argc, char* argv[]) {
    // Here, you create your logger dependency:
    std::unique_ptr<ILogger> concreteLogger = std::make_unique<Logger>();
    concreteLogger->set_name("frederic");

    // Here, you inject it. From here on, you will inject it everywhere
    // in your code. The using code will have no idea that under the hood,
    // you really are using the Logger implementation:
    some = new Something(concreteLogger.get());

    // Note: if you use `new`, do not forget to use `delete` as well. Otherwise,
    //       check out std::unique_ptr, like above.

    return 0;
}

这样做的好处是您现在可以更改记录器的实现 在任何时候都无需关心它(main 除外)。您还可以创建 如果您想进行单元测试,请模拟您的记录器 Something。这是高度 比在单元测试中处理单例更灵活,这在术语中将 制造各种(难以investigate/resolve)的问题。换句话说,这解决了 上面提到的第二个问题

请注意,DI 的一个可能缺点是您最终可能会有很多 参数,但在我看来它仍然优于使用单例。