由于从未提出过解决方案,因此我发布了一个总结我的研究和最终解决方案的答案。大多数情况下,我鼓励使用命名空间,因为正确使用命名空间会消除冲突。然而,Arduino 环境试图让事情变得简单以降低进入门槛,避开 C++ 的“复杂”特性,因此更高级的用例可能会继续遇到这样的问题。从其他 SO 答案和论坛帖子(我可以引用的地方)中,这里有一些方法可以避免这样的名称冲突:
如果您可以编辑源
编辑源代码以消除冲突或将命名空间添加到两个库之一。如果这是一个开源库,请提交拉取请求。这是最干净的解决方案。但是,如果您无法将更改推送回上游(例如当某个硬件是某些硬件的系统库时),那么当维护者/开发人员更新库时,您最终可能会遇到合并问题。
如果您无法编辑源
部分归功于:如何避免 C++ 中两个库的变量/函数冲突
对于只有头文件的库(或所有函数都是inline
)
(即,他们只有一个.h
没有.o
or的文件.cpp
)
将库包含在命名空间中。在大多数代码中,这被认为是糟糕的形式,但如果您已经处于尝试处理不能很好地包含自身的库的情况下,那么将代码包含在命名空间并避免名称冲突。
main.cpp
namespace foo {
#include library.h
}
int main() {
foo::bar(1);
}
对于具有函数的库
上述方法在编译时将无法链接,因为标头中的声明将在命名空间内,但这些函数的定义不在。
相反,创建一个包装头和实现文件。在标题中,声明您希望使用的命名空间和函数,但不要导入原始库。在实现文件中,导入您的库,并使用新命名空间函数中的函数。这样,一个冲突的库就不会与另一个库导入到相同的位置。
wrapper.h
namespace foo {
int bar(int a);
}
wrapper.cpp
#include "wrapper.h"
#include "library.h"
namespace foo {
int bar(int a) {
return ::bar(a);
}
}
main.cpp
#include "wrapper.h"
int main() {
foo::bar(1);
}
为了保持一致性,您还可以包装这两个库,以便它们各自位于各自的命名空间中。这种方法确实意味着您必须努力为您计划使用的每个函数编写一个包装器。但是,当您需要使用库中的类时,这会变得更加复杂(见下文)。
对于有类的库
这是上述包装函数模型的扩展,但您需要做更多的工作,并且还有一些缺点。您不能编写从库的类继承的类,因为这需要在定义类之前将原始库导入包装器标头中,因此您必须编写一个完整的包装器类。出于同样的原因,您也不能拥有可以委托调用的原始类的类型的私有成员。我在问题中描述的使用前向声明的尝试也没有奏效,因为头文件需要完整的类声明才能编译。这给我留下了下面的实现,它只适用于单例的情况(无论如何这是我的用例)。
包装头文件应该几乎完全复制您要使用的类的公共接口。
wrapper.h
namespace foo {
Class Bar() {
public:
void f(int a);
bool g(char* b, int c, bool d);
char* h();
};
}
包装器实现文件然后创建一个实例并传递调用。
wrapper.cpp
#include "wrapper.h"
#include "library.h"
namespace foo {
::Bar obj;
void Bar::f(int a) {
return obj.f(a);
}
bool Bar::g(char* b, int c, bool d) {
return obj.g(b, c, d);
}
char* Bar::h() {
return obj.h();
}
}
主文件将仅与原始类的单个实例交互,无论您的包装类实例化多少次。
main.cpp
#include "wrapper.h"
int main() {
foo::Bar obj;
obj.f(1);
obj.g("hello",5,true);
obj.h();
}
总的来说,这让我觉得这是一个有缺陷的解决方案。为了完全包装这个类,我认为可以修改它以添加一个工厂类,该类将完全包含在包装器实现文件中。每次实例化包装类时,此类都会实例化原始库类,然后跟踪这些实例。通过这种方式,您的包装类可以在工厂中保留与其关联实例的索引,并绕过将该实例作为其自己的私有成员的需要。这似乎是大量的工作,我没有尝试这样做,但看起来像下面的代码。(这可能需要一些润色并真正了解它的内存使用情况!)
包装头文件添加构造函数和私有成员来存储实例 id
wrapper.h
namespace foo {
Class Bar() {
public:
Bar();
void f(int a);
bool g(char* b, int c, bool d);
char* h();
private:
unsigned int instance;
};
}
包装器实现文件然后添加一个工厂类来管理原始库类的实例
wrapper.cpp
#include "wrapper.h"
#include "library.h"
namespace foo {
class BarFactory {
public:
static unsigned int new() {
instances[count] = new ::Bar();
return count++;
}
static ::Bar* get(unsigned int i) {
return instances[i];
}
private:
BarFactory();
::Bar* instances[MAX_COUNT]
int count;
};
void Bar::Bar() {
instance = BarFactory.new();
}
void Bar::f(int a) {
return BarFactory.get(i)->f(a);
}
bool Bar::g(char* b, int c, bool d) {
return BarFactory.get(i)->g(b, c, d);
}
char* Bar::h() {
return BarFactory.get(i)->h();
}
}
主文件保持不变
main.cpp
#include "wrapper.h"
int main() {
foo::bar obj;
obj.f(1);
obj.g("hello",5,true);
obj.h();
}
如果所有这些看起来工作量很大,那么你的想法和我一样。我实现了基本的类包装器,并意识到它不适用于我的用例。并且考虑到 Arduino 的硬件限制,我最终决定与其添加更多代码以便能够使用HTTPClient
任一库中的实现,我最终编写了自己的 HTTP 实现库,因此没有使用上述任何一个并保存了几个数百千字节的内存。但我想在这里分享,以防其他人想要回答同样的问题!