注意:如果你想把这个问题标记为一个副本,请花点时间阅读整个问题。我本人在下面引用了其他相关问题,但这些问题都是在C++语言级别上提出的。这个问题也在C++语言级别,但这个问题也是关于API的API开发,涉及到其他问题似乎没有解决的模板。
我向用户公开了一个API (客户端代码)。我的API非常依赖于模板。
什么不起作用
下面是我的API代码:
// api.h
#include <vector>
template<typename T> void api_func(std::vector<T> v);// api.cpp
#include <iostream>
#include <vector>
template<typename T> void api_func(std::vector<T> v)
{
// This prints just the size, but in the actual API, we would be
// doing more complex things.
std::cout << v.size() << '\n';
}下面是一个可能的客户端代码:
// client.cpp
#include "api.h"
int main()
{
std::vector<int> v {1, 2, 3, 4, 5};
// Although this client is calling the API with a std::vector<int>
// another client may call the API with another vector type such as
// std::vector<std::string>.
api_func(v);
}当然,这并不能编译。
$ clang++ -std=c++11 api.cpp client.cpp
Undefined symbols for architecture x86_64:
"void api_func<int>(std::__1::vector<int, std::__1::allocator<int> >)", referenced from:
_main in client-2a152c.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)什么起作用
这一问题已经在以下文章中对堆栈溢出进行了广泛的讨论:
根据这些讨论,解决问题的一种方法是将模板定义也移到头文件中。
// api.h - fixed
#include <iostream>
#include <vector>
template<typename T> void api_func(std::vector<T> v)
{
// This prints just the size, but in the actual API, we would be
// doing more complex things.
std::cout << v.size() << '\n';
}# api.cpp is unnecessary now
rm api.cpp
# client.cpp remains the same这已经编译好了。
$ clang++ -std=c++11 client.cpp && ./a.out
5问题
解析表明,如果我不想为API中的模板参数提交特定类型(以确保客户端有选择适合其需要的类型的灵活性),那么任何依赖于模板参数的函数都需要在头文件本身中定义。
但是,当我对API中的所有函数执行此操作时,我的所有业务逻辑似乎都移到了头文件中。所以我的整个API现在是一个庞大的.H文件集合,绝对没有.CPP文件。
我的问题:
发布于 2018-05-18 18:46:43
你已经从你的问题中删除了决定如何解决问题的部分。模板的主体关系到如何准确地隐藏它们(如果可能的话)。但我会用你给我的回答。
你的问题基本上是类型擦除的问题。
从以下几点开始:
template<typename T> void api_func(std::vector<T> v);这不会擦除有关类型v的任何信息。
但我们用的是:
template<typename T> void api_func(std::vector<T> v) {
// This prints just the size, but in the actual API, we would be
// doing more complex things.
std::cout << v.size() << '\n';
}这里,我们从向量中提取.size(),然后操作它。
然后,我们将其流到cout并不是传递类型的事实,而是我们期望它有一个.size()的事实。
struct has_dot_size_ref {
template<class T,
std::enable_if_t< !std::is_same<T, has_dot_size_ref>{}, bool> = true
>
has_dot_size_ref( T const& t ):
ptr( std::addressof(t) ),
call_dot_size([](void const* ptr)->std::size_t{
return static_cast<T const*>(ptr)->size();
})
{}
std::size_t size() const {
return call_dot_size(ptr);
}
private:
void const* ptr = 0;
std::size_t(*call_dot_size)(void const*) = 0;
};这是一个类型橡皮。它使用它的参数,并删除有关它的所有内容,除了对象有一个返回std::size_t的std::size_t成员这一事实。
现在在标题中:
void api_func(has_dot_size_ref v);在cpp文件中:
void api_func(has_dot_size_ref v) {
// This prints just the size, but in the actual API, we would be
// doing more complex things.
std::cout << v.size() << '\n';
}现在密码起作用了。
如果您正在做一些更复杂的事情,请从所讨论的类型中计算出您到底需要什么,将其擦除到这些属性。
有些代码保留在头文件中,而另一些则不存在。
不是每个问题都可以通过这种方式删除类型;有时类型擦除就是整个方法。而这些技术,因为它们是人工的,需要做一些工作。此外,类型擦除屏障可能很昂贵(为此,有时键入擦除范围操作而不是元素操作;甚至从书面元素操作合成类型擦除范围操作)。
https://stackoverflow.com/questions/50417440
复制相似问题