【问题标题】:How to share objects in c++ across libraries如何在 C++ 中跨库共享对象
【发布时间】:2021-04-26 03:37:10
【问题描述】:

假设我有一个这样的程序:

文件 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 中的logger-&gt;log() 中失败,因为从未定义过logger。我可以通过添加logger = new Logger() 来解决这个问题。但我只想创建一个新的Logger 实例,如果在使用该库的程序/库中没有创建任何实例。当已经创建了一个实例时,我可以通过添加extern Logger logger; 来使用它。但是,当 no 实例已创建时,这将不起作用。有什么建议(有可能吗?)?

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

【问题讨论】:

  • “但我只想创建一个新的 Logger 实例,如果还没有创建...” - 这听起来像单例设计模式,除非我没有正确理解你的问题?
  • @BobMorane 你确实理解正确
  • 好的,既然你似乎可以控制记录器类,也许你应该make it a singleton。如果您这样做,请注意,尽管单例也具有允许全局访问的副作用。但是,对于记录器,这可能没问题。
  • 感谢@BobMorane 一如既往地为我指明了正确的方向;)但是,不同的库/程序中是否可以使用单例?
  • 什么意思?如果记录器是它的库,只需链接到它即可使用它。除非你有其他考虑?

标签: c++ object logging gtk gtkmm


【解决方案1】:

第一种方法:单例

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

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

在编写高质量软件时,哪些是真正的问题。此外,对于您的特定 案例,请务必阅读this answer,其中解释了如何确保一切 被适当地链接,所以你最终不会得到你的多个实例 单身人士。

第二种方法:依赖注入

我决定在这里发布一个答案来说明另一种做事方式 解决了上面提到的两个问题:dependency injection (DI)。使用 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 类编写一个接口并使用它来代替:

// 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 进行单元测试,请模拟您的记录器。这是高度 比在单元测试中处理单例更灵活,这在术语上将 制造各种(难以调查/解决)问题。这,在条款,解决 上面提到的第二个问题。

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

【讨论】:

  • 我认为这应该成为一个社区 wiki 答案,因为可能还有其他人有同样的问题
  • 如果我想添加析构函数,我应该将其设为私有还是公有?
  • 在 Logger 类中?
  • 我的意思是,每当我实现一个单例时,析构函数应该像构造函数一样是私有的,还是像实例方法一样是公共的
  • 我什至在使用 C++17
猜你喜欢
  • 1970-01-01
  • 2015-02-28
  • 2010-10-20
  • 2023-02-06
  • 1970-01-01
  • 1970-01-01
  • 2015-02-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多