我正在设计C ++静态库。 我想使类通用/可配置,以便它们可以支持许多数据类型(我不想在我的库中编写任何特定于数据类型的代码)。 所以我把课程模板化了。
但由于我目前正在使用的编译器不支持C ++“导出”模板功能,因此我不得不在头文件中提供类的实现。 我不想将我的类的实现细节暴露给将要使用我的库的客户端代码。
您能否为我提供上述问题的一些设计替代方案?
答案 0 :(得分:3)
在模板之前,必须使用运行时多态来编写与类型无关的C ++代码。但是对于模板,您可以将这两种技术结合起来。
例如,假设您要存储任何类型的值,以便以后检索。没有模板,你必须这样做:
struct PrintableThing
{
// declare abstract operations needed on the type
virtual void print(std::ostream &os) = 0;
// polymorphic base class needs virtual destructor
virtual ~PrintableThing() {}
};
class PrintableContainer
{
PrintableThing *printableThing;
public:
// various other secret stuff
void store(PrintableThing *p);
};
此库的用户必须手动编写自己的PrintableThing
派生版本,以包装自己的数据并在其上实现print
函数。
但是你可以围绕这样一个系统包装一个基于模板的层:
template <T>
struct PrintableType : PrintableThing
{
T instance;
virtual void print(std::ostream &os)
{ os << instance; }
PrintableType(const T &i)
: instance(i) {}
};
并在PrintableContainer
类的声明中在库的 header 中添加一个方法:
template <class T>
void store(const T &p)
{
store(new PrintableType(p));
}
这充当了模板和运行时多态之间的桥梁,编译时绑定到<<
运算符以实现print
,以及复制构造函数(当然也转发到嵌套)实例的析构函数)。
通过这种方式,您可以完全基于运行时多态性编写库,其实现能够隐藏在库的源代码中,但添加了一些模板“sugar”以方便使用
这是否值得麻烦将取决于您的需求。它具有纯粹的技术优势,因为运行时多态性有时正是您所需要的。在缺点方面,您无疑会降低编译器有效内联的能力。从好的方面来说,您的编译时间和二进制代码膨胀可能会下降。
示例是std::tr1::function
和boost::any
,它们具有非常干净,现代的基于C ++模板的前端,但作为运行时多态容器在幕后工作。
答案 1 :(得分:1)
我有一些消息给你,伙计。即使使用export
,您仍然需要释放所有模板代码 - export
只是让您不必将定义放在头文件中。你完全陷入了困境。您可以使用的唯一技术是拆分一些非模板函数并将它们放入不同的类中。但这很丑陋,通常涉及void*
和展示位置new
和delete
。这只是野兽的本质。
答案 2 :(得分:0)
您可以尝试对代码进行模糊处理 - 但除了在头文件中包含模板代码外,您在C ++ 03中别无选择。
Vandevoorde在他的书中描述了另一种技术:显式实例化 - 但这需要明确地实例化所有可能有用的组合。
但是,对于本主题的最全面的评论,请阅读C ++模板:完整指南中的第6章。
编辑(响应您的评论):您可以使用两种方法编写通用代码,而无需使用模板:
1)预处理器 - 仍然需要头文件
2)使用void * - yuk - 令人难以置信的不安全
所以不,我不建议不使用模板来解决模板专门设计的问题(尽管有些缺陷)。
答案 3 :(得分:0)
模板的一个问题是它们需要编译代码。您永远不知道最终用户将如何专门化/实例化您的模板,因此您的dll文件必须包含编译形式的所有可能的模板特化。这意味着要导出对&lt; X,Y&gt;模板你必须强制对&lt; int,float&gt;,pair&lt; int,string&gt;,pair&lt; string,HWND&gt;的compilication等等......到无穷大......
我想更实用的解决方案是取消私有/隐藏代码的模板。您可以创建仅为单个模板特化编译的特殊内部函数。在下面的示例中,永远不会从MyClass调用internal_foo-function,其中A不是int。
template<class A>
class MyClass
{
int a;
float b;
A c;
int foo(string param1);
{
((MyClass<int>*)this)->internal_foo(param1);
}
int internal_foo(string param1); // only called on MyClass<int> instances
};
template<>
__declspec(dllexport) int MyClass<int>::internal_foo(string param1)
{
... secret code ...
}
这当然是一个黑客。使用它时,你应该格外小心,不要使用成员变量“c”,因为它并不总是整数(即使internal_foo认为它是)。你甚至无法用断言保护自己。 C ++允许你用脚射击自己,并且在你为时已晚之前没有任何关于它的迹象。
PS。我没有测试过这段代码,因此可能需要进行一些微调。不确定例如是否需要__declspec(dllimport)以便编译器从dll-file中找到internal_foo函数...
答案 4 :(得分:0)
使用模板,您无法避免传送代码(除非您的代码仅适用于一组固定的类型,在这种情况下您可以显式实例化)。在我工作的地方,我们有一个必须处理POD类型的库(CORBA / DDS / HLA数据定义),所以最后我们发布了模板。
模板将大部分代码委托给以二进制形式提供的非模板化代码。在某些情况下,工作必须直接在传递给模板的类型中完成,因此不能委托给非模板化代码,因此它不是一个完美的解决方案,但它隐藏了足够的部分代码来成为我们的CEO很高兴(项目负责人很乐意提供模板中的所有代码)。
正如尼尔在对这个问题的评论中指出的那样,在绝大多数情况下,代码中没有任何神奇的内容无法被其他人重写。