首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何防止带有模板的整个API成为只包含.h文件的集合?

如何防止带有模板的整个API成为只包含.h文件的集合?
EN

Stack Overflow用户
提问于 2018-05-18 18:39:51
回答 1查看 218关注 0票数 1

注意:如果你想把这个问题标记为一个副本,请花点时间阅读整个问题。我本人在下面引用了其他相关问题,但这些问题都是在C++语言级别上提出的。这个问题也在C++语言级别,但这个问题也是关于API的API开发,涉及到其他问题似乎没有解决的模板。

我向用户公开了一个API (客户端代码)。我的API非常依赖于模板。

什么不起作用

下面是我的API代码:

代码语言:javascript
复制
// api.h
#include <vector>

template<typename T> void api_func(std::vector<T> v);
代码语言:javascript
复制
// 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';
}

下面是一个可能的客户端代码:

代码语言:javascript
复制
// 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);
}

当然,这并不能编译。

代码语言:javascript
复制
$ 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)

什么起作用

这一问题已经在以下文章中对堆栈溢出进行了广泛的讨论:

根据这些讨论,解决问题的一种方法是将模板定义也移到头文件中。

代码语言:javascript
复制
// 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';
}
代码语言:javascript
复制
# api.cpp is unnecessary now
rm api.cpp
# client.cpp remains the same

这已经编译好了。

代码语言:javascript
复制
$ clang++ -std=c++11 client.cpp && ./a.out 
5

问题

解析表明,如果我不想为API中的模板参数提交特定类型(以确保客户端有选择适合其需要的类型的灵活性),那么任何依赖于模板参数的函数都需要在头文件本身中定义。

但是,当我对API中的所有函数执行此操作时,我的所有业务逻辑似乎都移到了头文件中。所以我的整个API现在是一个庞大的.H文件集合,绝对没有.CPP文件。

我的问题:

  • 我是不是做错了什么,导致我的整个API实现只由.H文件而不是.CPP文件组成?
  • 这样的API是如何设计的呢?是否有其他技术将实现代码带回到.CPP文件中,以使.H文件中只有尽可能少的东西?
EN

回答 1

Stack Overflow用户

发布于 2018-05-18 18:46:43

你已经从你的问题中删除了决定如何解决问题的部分。模板的主体关系到如何准确地隐藏它们(如果可能的话)。但我会用你给我的回答。

你的问题基本上是类型擦除的问题。

从以下几点开始:

代码语言:javascript
复制
template<typename T> void api_func(std::vector<T> v);

这不会擦除有关类型v的任何信息。

但我们用的是:

代码语言:javascript
复制
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()的事实。

代码语言:javascript
复制
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_tstd::size_t成员这一事实。

现在在标题中:

代码语言:javascript
复制
void api_func(has_dot_size_ref v);

在cpp文件中:

代码语言:javascript
复制
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';
}

现在密码起作用了。

如果您正在做一些更复杂的事情,请从所讨论的类型中计算出您到底需要什么,将其擦除到这些属性。

有些代码保留在头文件中,而另一些则不存在。

不是每个问题都可以通过这种方式删除类型;有时类型擦除就是整个方法。而这些技术,因为它们是人工的,需要做一些工作。此外,类型擦除屏障可能很昂贵(为此,有时键入擦除范围操作而不是元素操作;甚至从书面元素操作合成类型擦除范围操作)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50417440

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档