如何隐藏实现助手模板?

时间:2011-05-03 12:32:17

标签: c++ templates

假设我在头文件中声明了两个模板函数:

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

并且假设这些函数的实现(也在头文件而不是源文件中,因为它们是模板)使用了一些实现辅助函数,它也是一个模板:

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

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

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

在我包含头文件的任何源文件中,辅助函数都是可见的。我不希望这样,因为辅助函数只是一个实现细节。有没有办法隐藏辅助功能?

7 个答案:

答案 0 :(得分:20)

一种常见的方法(例如,在许多Boost库中使用)是将帮助器放在名为details的名称空间中,可能位于单独的标头中(包含在&#34; public&#34;标头中) )。

没有办法阻止它可见和可调用,但这清楚地表明它是实现的一部分,而不是接口。

答案 1 :(得分:4)

我头顶的两个选项:

  1. 将所有实现移动到h文件中,该文件包含在h文件的底部。
  2. 将您的代码重构为类模板,然后将帮助程序设为私有。

答案 2 :(得分:3)

由于代码的用户需要查看func1函数的完整定义,因此可以隐藏它的实现,也不是它的辅助函数实现。

但是如果将实现移动到另一个文件,则用户只需面对模板声明

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

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

定义:

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

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

答案 3 :(得分:3)

既定的先例是将这种事物放在一个特殊的(即一致的)命名嵌套命名空间中。 Boost使用namespace details,Loki使用namespace Private。显然,没有任何东西可以阻止任何人使用这些名称空间的内容,但这两个名称都表达了其内容不是一般消费的含义。

话虽如此,一个简单的替代方法是将func1func2从自由函数模板转换为某些公共类的静态成员函数模板;这样,helper可以简单地成为所述类的私有成员,对于外部世界是不可见的:

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

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

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

答案 4 :(得分:1)

我会(如前所述)创建一个模板类,使所有函数保持静态,并将辅助函数设为私有。但除此之外,我还建议将构造函数设为私有,如下所示:

template <typename T>
class Foo{
public:
  static void func1(const T& value);
  static void func2(const T& value);
private:
  Foo();
  static void helper(const T& 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
}

那么为什么会有人想要这个呢?因为具有完全相同的不同实例是您可能永远不会想要的。当编译器告诉你某个类的构造函数是私有的时,你可以假设它的不同实例是不必要的。

答案 5 :(得分:1)

C++20 中,您现在可以使用 modules。 为此,您可以创建一个模块 some_module.cppm 保存所有函数并仅标记接口(单个函数或命名空间)使用 export 而不暴露辅助函数:

// some_module.cppm
export module some_module;

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

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

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

在上面的例子中,只有函数 func1func2 被导出并且可以在 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 :(得分:1)

我知道你的意思是你想把它隐藏得非常好,只要调用者不改变你的代码文件,他们就无法找到你的辅助函数。我非常了解它,因为我最近有非常相似的需求。

那么如何将您的辅助函数包装在匿名命名空间中?这是最近发现的最优雅的款式:

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

这种做法有效地对外部隐藏了您的内部功能。我找不到调用者可以访问匿名命名空间中的函数的任何方式。

正如@user3635700 所回答的那样,将命名空间转换为充满静态函数的类通常不是一个好习惯,尤其是当您有静态变量模板时,例如:

template <uint8_t TimerCode>
static uint16_t MillisecondsElapsed;

如果这个变量出现在一个类中,你必须在类之外的某个地方初始化它!但是,你不能这样做,因为它是一个模板!跆拳道!

头文件中的匿名命名空间似乎被一些人批评,是满足我们需求的唯一也是最完美的解决方案。