您可以将对全局对象的引用用作模板参数。例如:
class A {};
template<A& x>
void fun()
{
}
A alpha;
int main()
{
fun<alpha>();
}
Run Code Online (Sandbox Code Playgroud)
在什么情况下引用模板参数可能有用?
lub*_*bgr 18
一种情况可能是带有身份令牌的强类型定义,该身份令牌不应该是整数类型,而应该是一个字符串,以便在序列化东西时易于使用。然后,您可以利用空的基类优化来消除派生类型具有的任何其他空间要求。
例:
// File: id.h
#pragma once
#include <iosfwd>
#include <string_view>
template<const std::string_view& value>
class Id {
// Some functionality, using the non-type template parameter...
// (with an int parameter, we would have some ugly branching here)
friend std::ostream& operator <<(std::ostream& os, const Id& d)
{
return os << value;
}
// Prevent UB through non-virtual dtor deletion:
protected:
~Id() = default;
};
inline const std::string_view str1{"Some string"};
inline const std::string_view str2{"Another strinng"};
Run Code Online (Sandbox Code Playgroud)
在某些翻译单元中:
#include <iostream>
#include "id.h"
// This type has a string-ish identity encoded in its static type info,
// but its size isn't augmented by the base class:
struct SomeType : public Id<str2> {};
SomeType x;
std::cout << x << "\n";
Run Code Online (Sandbox Code Playgroud)
引用非类型模板参数允许您编写代码,这些代码将自动专门用于处理静态存储持续时间的特定对象。这在例如需要静态分配资源的环境中非常有用。假设我们有一些Processor
类应该进行某种处理,涉及动态创建一堆对象。此外,假设这些对象的存储应该来自静态分配的内存池。我们可能有一个非常简单的分配器,只包含一些存储空间和一个指向可用空间开头的“指针”
template <std::size_t SIZE>\nclass BumpPoolAllocator\n{\n char pool[SIZE];\n\n std::size_t next = 0;\n\n void* alloc(std::size_t alignment)\n {\n void* ptr = pool + next;\n next = ((next + alignment - 1) / alignment * alignment);\n return ptr;\n }\n\npublic:\n template <typename T, typename... Args>\n T& alloc(Args&&... args)\n {\n return *new (alloc(alignof(T))) T(std::forward<Args>(args)...);\n }\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n然后通过将实例放置在静态存储中的某个位置来静态分配一定大小的内存池:
\n\nBumpPoolAllocator<1024*1024> pool_1;\n
Run Code Online (Sandbox Code Playgroud)\n\n现在,我们可以拥有一个Processor
可以与任何类型的内存池一起使用的
template <typename T, typename Pool>\nclass Processor\n{\n Pool& pool;\n\n // \xe2\x80\xa6\n\npublic:\n Processor(Pool& pool) : pool(pool) {}\n\n void process()\n {\n // \xe2\x80\xa6\n\n auto bla = &pool.template alloc<T>();\n\n // \xe2\x80\xa6\n }\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n然后静态分配其中之一
\n\nProcessor<int, decltype(pool_1)> processor_1(pool_1);\n
Run Code Online (Sandbox Code Playgroud)\n\n但请注意 now 的每个此类实例Processor
本质上都包含一个保存池对象地址的字段,该对象实际上是编译时已知的常量。每次我们Processor
对其进行任何操作时pool
,都会从内存中获取 the 的地址pool
,以便始终访问位于编译时实际上已知的地址的同一池对象。如果我们已经静态分配所有内容,那么我们不妨利用编译时已知所有内容的位置这一事实来消除不必要的间接寻址。使用参考模板参数,我们可以做到这一点:
template <typename T, auto& pool>\nclass Processor\n{\n // \xe2\x80\xa6\n\npublic:\n void process()\n {\n // \xe2\x80\xa6\n\n auto bla = &pool.template alloc<T>();\n\n // \xe2\x80\xa6\n }\n};\n\nProcessor<int, pool_1> processor_1;\n
Run Code Online (Sandbox Code Playgroud)\n\n我们不是让每个Processor
对象都保留它应该使用的池的地址,而是将整个对象专门化为Processor
直接使用特定的池对象。这使我们能够摆脱任何不必要的间接,要使用的池的地址本质上只是在任何地方内联。同时,我们保留以我们希望的任何方式自由组合池和处理器的灵活性:
BumpPoolAllocator<1024*1024> pool_1; // some pool\nBumpPoolAllocator<4*1024> pool_2; // another, smaller pool\n\n\nProcessor<int, pool_1> processor_1; // some processor\n\nstruct Data {};\nProcessor<Data, pool_1> processor_2; // another processor using the same pool\n\nProcessor<char, pool_2> processor_3; // another processor using the smaller pool\n
Run Code Online (Sandbox Code Playgroud)\n\n我发现自己一直以这种方式使用参考模板参数的一个环境是 GPU。在许多情况下,一般模板(特别是参考模板参数)成为 GPU 编程的极其强大(我什至可以说:必不可少)的工具。首先,编写 GPU 代码的唯一原因是性能。从某些全局通用堆进行动态内存分配通常不是 GPU 上的一个选项(大量开销)。每当需要动态资源分配时,通常会使用一些专门构建的有界池来完成。与使用运行时值指针算术执行相同的操作相比,使用相对于静态基地址的偏移量可能是有益的(如果 32 位索引足够),因为 GPU 通常具有 32 位寄存器,并且使用的寄存器数量可以是可以实现的并行性水平的限制因素。因此,静态分配资源并摆脱间接寻址通常对 GPU 代码很有吸引力。同时,间接函数调用的成本通常在 GPU 上过高(由于必须保存和恢复的状态量),这意味着使用运行时多态性来实现灵活性通常是不可能的。带有引用模板参数的模板正是我们所需要的:能够以一种完全灵活的方式表达对复杂数据结构的复杂操作,直到您点击编译,但编译为最严格和最高效的二进制文件。
\n\n出于类似的原因,我认为参考模板参数非常有用,例如,也在嵌入式系统中......
\n 归档时间: |
|
查看次数: |
575 次 |
最近记录: |