Как сделать так, чтобы весь API с шаблонами не становился коллекцией только .h файлов? - PullRequest
0 голосов
/ 18 мая 2018

Примечание. Пожалуйста, уделите минутку и прочитайте весь вопрос, если у вас возникнет желание пометить его как дубликат.Я сам ссылался на другие связанные вопросы ниже, но эти вопросы в основном относятся к языку 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.

Мои вопросы:

  • Я делаю что-то не так, что вызвало всю мою реализацию APIбыть составленным только из файлов .H и без файлов .CPP?
  • Как обычно создаются такие API-интерфейсы?Существуют ли другие способы вернуть код реализации в файлы .CPP так, чтобы в файлы .H входило только минимально возможное количество?

1 Ответ

0 голосов
/ 18 мая 2018

Вы удалили из своего вопроса ту часть, которая определяет, как ее решить.Тело ваших шаблонов имеет значение , чтобы определить, как именно их скрыть (или, если это возможно).Но я отвечу тем, что вы мне дали.

Ваша проблема в основном заключается в удалении типа.

Начните с этого:

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;
};

, это ластик типов.Он берет свой аргумент и стирает в нем все, кроме того факта, что у объекта есть .size() член, который возвращает 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';
}

и теперь код работает.

Если вы делаете что-то более сложное, выясните, что именно точно вам нужно из рассматриваемого типасотрите до точно этих свойств.

Некоторые из ваших кодов остаются в заголовочных файлах, другие нет.

Не все проблемы могут быть стерты таким образом;иногда стирание типа - это весь метод.И эти методы, потому что они ручные, требуют немного работы.Кроме того, барьер стирания типа может быть дорогостоящим (для этого иногда вы вводите операции стирания диапазона вместо операций элемента или даже синтезируете операцию стирания типа диапазона из записанного элементаоперация).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...