如何跨库共享 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 的一个可能缺点是您最终可能会有很多
参数,但在我看来它仍然优于使用单例。
假设我有这样一个程序:
文件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 的一个可能缺点是您最终可能会有很多 参数,但在我看来它仍然优于使用单例。