Как скрыть вспомогательный шаблон реализации?

#c #templates

#c #шаблоны

Вопрос:

Предположим, что у меня есть две шаблонные функции, объявленные в заголовочном файле:

 template <typename T> void func1(const Tamp; value);
template <typename T> void func2(const Tamp; value);
  

И предположим, что реализация этих функций (также в файле заголовка, а не в исходном файле, поскольку они являются шаблонами) использует некоторую вспомогательную функцию реализации, которая также является шаблоном:

 template <typename T> void helper(const Tamp; value) {
    // ...
}

template <typename T> void func1(const Tamp; value) {
    // ...
    helper(value);
}

template <typename T> void func2(const Tamp; value) {
    // ...
    helper(value);
}
  

В любом исходном файле, который я включаю в файл заголовка, вспомогательная функция будет видна. Я не хочу этого, потому что вспомогательная функция — это просто деталь реализации. Есть ли способ скрыть вспомогательную функцию?

Комментарии:

1. что вы имеете в виду, что это видно? как вы можете это назвать?

2. @AtoMerZ Да, вы можете вызвать его; имя helper будет определено в любом исходном файле, куда вы включаете файл заголовка. Он должен быть скрыт, чтобы только func1 и func2 знали, что он существует, а остальная часть программы — нет.

3. @Jsper, я бы порекомендовал подход Джона Диблинга.

4. Это то, для чего export было хорошо. К сожалению, только один поставщик реализовал его, и теперь он устарел.

Ответ №1:

Общий подход (используемый, например, во многих библиотеках Boost) заключается в размещении помощника в пространстве имен с именем details , возможно, в отдельном заголовке (включенном из заголовка «public»).

Нет способа предотвратить его видимость и возможность вызова, но это довольно ясно указывает на то, что он является частью реализации, а не интерфейса.

Ответ №2:

Установленный прецедент заключается в размещении такого рода вещей в специально (т. Е. последовательно) именованном вложенном пространстве имен. Boost использует namespace details , Loki использует namespace Private . Очевидно, что ничто не может помешать кому-либо использовать содержимое этих пространств имен, но оба названия передают смысл того, что их содержимое не предназначено для общего использования.

При этом простой альтернативой является преобразование func1 и func2 из шаблонов свободных функций в шаблоны статических функций-членов некоторого общего класса; таким образом, helper может быть просто закрытым членом указанного класса, невидимым для внешнего мира:

 struct funcs {
    template<typename T>
    static void func1(T constamp; value) {
        // ...
        helper(value);
    }

    template<typename T>
    static void func2(T constamp; value) {
        // ...
        helper(value);
    }

private:
    template<typename T>
    static void helper(T constamp; value) {
        // ...
    }
};
  

Ответ №3:

Два варианта, которые приходят мне в голову:

  1. Переместите всю реализацию в файл hpp, который вы включаете в нижнюю часть вашего файла h.
  2. Реорганизуйте свой код в виде шаблонов классов, затем сделайте помощников закрытыми.

Комментарии:

1. Я не понимаю, как первый вариант мог бы что-то решить. Этот файл hpp все равно будет включен в модуль компиляции, все, что вы объявите в нем, все равно будет видно в исходном файле, из которого вы его включаете.

2. Я пробовал 1) раньше и пришел к пониманию, что если бы они были в нижней части, мне все равно нужны были объявления forward для них вверху, чтобы иметь возможность их использовать.

Ответ №4:

Поскольку пользователю вашего кода необходимо видеть полное определение func1 функции, ни ее реализация, ни реализация вспомогательной функции не могут быть скрыты.

Но если вы переместите реализацию в другой файл, пользователю придется столкнуться только собъявлением шаблона.:

 //templates.h
template< typename T > void f1( Tamp; );

#include <templates_impl.h> // post-inclusion
  

И определение:

 // templates_impl.h
template< typename T > void f1_helper( Tamp; ) {
}

template< typename T > void f1( Tamp; ) {
   // the function body
}
  

Комментарии:

1. Я не думаю, что это работает. Когда я включаю его templates.h из моего .cpp файла, он templates_impl.h также будет включен (хотя и косвенно), и его содержимое будет видно в моем .cpp файле — так что f1_helper() оно не скрыто.

2. Тогда я не понимаю вашего ответа. Это просто для демонстрации способа, который не работает, или это решение?

3. Обратите внимание, что под «видимым» я имею в виду не просто «видимый людьми», я имею в виду «видимый компилятором», т.е. f1_helper это имя определенной функции.

4. @Jesper: Я пытаюсь показать, что вы не можете эффективно скрыть это. Но вы можете пометить его как «не предназначенный для пользователей», используя предложенный метод (как также показано по существу во всех других ответах). По сути, это то же самое, что добавить символ подчеркивания для обозначения закрытых переменных-членов в Python.

5. Джеспер идея, стоящая за этим, заключается в том, что вы даете понять людям, которые будут читать ваш файл заголовка , что эти функции являются отдельными. Все они по-прежнему остаются в области видимости, поэтому к ним можно получить программный доступ, но для кого-то, кто читает файл заголовка, чтобы увидеть, что можно вызывать, а что нет, это сильный намек на то, чтобы не вызывать напрямую эти другие. Тем не менее, если все, что вам нужно, это подсказка для людей, читающих ваш файл, тогда комментарии тоже будут работать, как и тщательно подобранные имена функций.

Ответ №5:

В C 20 теперь можно использовать модули. Для этого вы могли бы создать модуль, some_module.cppm содержащий все функции и отмечающий только интерфейс (либо отдельные функции, либо пространство имен) с export , не предоставляя при этом вспомогательные функции:

 // some_module.cppm
export module some_module;

template <typename T> 
void helper(const Tamp; value) {
  // ...
}

export
template <typename T>
void func1(const Tamp; value) {
  // ...
  helper(value);
}

export
template <typename T>
void func2(const Tamp; value) {
  // ...
  helper(value);
}
  

В приведенном выше случае экспортируются только функции func1 и func2 , к которым можно получить доступ внутри main.cpp :

 // main.cpp
#include <cstdlib>
import some_module;

int main() {
  func1(1.0);
  func2(2.0);
  return EXIT_SUCCESS;
}
  

В clang 12 вы можете скомпилировать этот код с помощью следующих трех команд:

 clang   -std=c  2b -fmodules-ts --precompile some_module.cppm -o some_module.pcm
clang   -std=c  2b -fmodules-ts -c some_module.pcm -o some_module.o
clang   -std=c  2b -fmodules-ts -fprebuilt-module-path=. some_module.o main.cpp -o main
./main
  

Ответ №6:

Я бы (как говорилось ранее) создал класс шаблона, сделал все функции статическими, а вспомогательную функцию закрытой. Но помимо этого я бы также рекомендовал сделать конструктор закрытым, как показано ниже:

 template <typename T>
class Foo{
public:
  static void func1(const Tamp; value);
  static void func2(const Tamp; value);
private:
  Foo();
  static void helper(const Tamp; value);
}
  

Когда вы делаете конструктор закрытым, компилятор не разрешает экземпляры этого шаблонного класса. Таким образом, приведенный ниже код станет незаконным:

 #include "foo.h"

int main(){
  int number = 0;
  Foo<int>::func1(number); //allowed
  Foo<int>::func2(number); //allowed
  Foo<int>::helper(number); //not allowed, because it's private
  Foo<int> foo_instance; //not allowed, because it's private
}
  

Итак, зачем кому-то это нужно? Потому что наличие разных экземпляров, которые являются ТОЧНО такими же, — это то, чего вы, вероятно, никогда не захотите. Когда компилятор сообщает вам, что конструктор некоторого класса является закрытым, тогда вы можете предположить, что наличие разных его экземпляров было бы излишним.

Ответ №7:

Я знаю, вы имеете в виду, что хотите скрыть его настолько тонко, чтобы вызывающие пользователи не могли найти ваши вспомогательные функции, пока они не изменят ваш файл кода. Я знаю это так хорошо, потому что в последнее время у меня очень похожие потребности.

Итак, как насчет переноса ваших вспомогательных функций в анонимное пространство имен? За последнее время это самый элегантный стиль, который я нашел:

 namespace YourModule
{
namespace
{//Your helper functions}
//Your public functions
}
  

Эта практика эффективно скрывает ваши внутренние функции извне. Я не могу найти никакого способа, которым вызывающий объект может получить доступ к функциям в анонимных пространствах имен.

Как ответил @user3635700 , обычно не рекомендуется преобразовывать ваше пространство имен в класс, полный статических функций, особенно когда у вас есть шаблоны статических переменных, таких как:

 template <uint8_t TimerCode>
static uint16_t MillisecondsElapsed;
  

Если эта переменная появляется в классе, вам придется инициализировать ее где-то за пределами класса! Однако вы не можете этого сделать, потому что это шаблон! ЧЕРТ возьми!

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