【问题标题】:Uses of pointers non-type template parameters?指针非类型模板参数的用途?
【发布时间】:2012-11-04 16:35:21
【问题描述】:

有没有人使用过指针/引用/指向成员(非类型)模板参数?
我不知道应该将 C++ 功能用作最佳实践的任何(理智/真实世界)场景。

功能演示(用于指针):

template <int* Pointer> struct SomeStruct {};
int someGlobal = 5;
SomeStruct<&someGlobal> someStruct; // legal c++ code, what's the use?

任何启示将不胜感激!

【问题讨论】:

  • @leemes:我认为他指的是template &lt;int* T&gt; ...
  • @PeterAlexander 嗯,这是一个有趣的问题。 +1
  • stackoverflow.com/questions/5687540/… - 接受的答案包含很好和有用的例子。
  • @icepack 接受的答案不包含任何有用的示例(仅包含标准中相关部分的规则)。投票第二高的答案包含一个示例,但我不会说它对于现实世界的代码非常实用。
  • 对不起,我的意思是第二个答案。字符串连接很有用。

标签: c++ templates


【解决方案1】:

指向函数的指针

指向成员函数的指针和指向函数的非类型参数对某些委托非常有用。它可以让你做出非常快速的委托。

例如:

#include <iostream>
struct CallIntDelegate
{
    virtual void operator()(int i) const = 0;
};

template<typename O, void (O::*func)(int)>
struct IntCaller : public CallIntDelegate
{
    IntCaller(O* obj) : object(obj) {}
    void operator()(int i) const
    {
        // This line can easily optimized by the compiler
        // in object->func(i) (= normal function call, not pointer-to-member call)
        // Pointer-to-member calls are slower than regular function calls
        (object->*func)(i);
    }
private:
    O* object;
};

void set(const CallIntDelegate& setValue)
{
    setValue(42);
}

class test
{
public:
    void printAnswer(int i)
    {
        std::cout << "The answer is " << 2 * i << "\n";
    }
};

int main()
{
    test obj;
    set(IntCaller<test,&test::printAnswer>(&obj));
}

Live example here.

指向数据的指针

您可以使用此类非类型参数来扩展变量的可见性。

例如,如果您正在编写反射库(这可能对脚本非常有用),使用宏让用户为库声明他的类,您可能希望将所有数据存储在一个复杂的结构中(这可能随着时间的推移而改变),并且想要一些句柄来使用它。

例子:

#include <iostream>
#include <memory>

struct complex_struct
{
    void (*doSmth)();
};

struct complex_struct_handle
{
    // functions
    virtual void doSmth() = 0;
};

template<complex_struct* S>
struct csh_imp : public complex_struct_handle
{
    // implement function using S
    void doSmth()
    {
        // Optimization: simple pointer-to-member call,
        // instead of:
        // retrieve pointer-to-member, then call it.
        // And I think it can even be more optimized by the compiler.
        S->doSmth();
    }
};

class test
{
    public:
        /* This function is generated by some macros
           The static variable is not made at class scope
           because the initialization of static class variables
           have to be done at namespace scope.

           IE:
               class blah
               {
                   SOME_MACRO(params)
               };
           instead of:
               class blah
               {
                   SOME_MACRO1(params)
               };
               SOME_MACRO2(blah,other_params);

           The pointer-to-data template parameter allows the variable
           to be used outside of the function.
        */
        std::auto_ptr<complex_struct_handle> getHandle() const
        {
            static complex_struct myStruct = { &test::print };
            return std::auto_ptr<complex_struct_handle>(new csh_imp<&myStruct>());
        }
        static void print()
        {
            std::cout << "print 42!\n";
        }
};

int main()
{
    test obj;
    obj.getHandle()->doSmth();
}

抱歉auto_ptrshared_ptr 在键盘和 Ideone 上均不可用。 Live example.

【讨论】:

  • 我实际上使用了指向成员的变体来实现'member-foreach'之类的东西。
  • 我个人将它们用于库(SPARK 粒子引擎)中的“信号槽”模块,它是 C++ 中非常强大的部分。
  • 关于指向函数的例子,有没有办法将指向 printAnswer 的指针保留在一个对象中,然后将该对象交给 set 函数以达到相同的结果?
  • @ALOToverflow 是的,但您还必须将指针传递给对象。棘手的部分是当此函数不知道对象的类型时(例如,函数在库中,对象来自您),因为调用指向函数的指针需要您知道对象的类型.因此所展示的代表可以解决这个问题。
【解决方案2】:

指向成员的指针与指向数据或引用的指针大不相同。

如果您想指定要调用的成员函数(或要访问的数据成员)但又不想将对象放在特定的层次结构中(否则虚拟方法是通常足够了)。

例如:

#include <stdio.h>

struct Button
{
    virtual ~Button() {}
    virtual void click() = 0;
};

template<class Receiver, void (Receiver::*action)()>
struct GuiButton : Button
{
    Receiver *receiver;
    GuiButton(Receiver *receiver) : receiver(receiver) { }
    void click() { (receiver->*action)(); }
};

// Note that Foo knows nothing about the gui library    
struct Foo
{
    void Action1() { puts("Action 1\n"); }
};

int main()
{
    Foo foo;
    Button *btn = new GuiButton<Foo, &Foo::Action1>(&foo);
    btn->click();
    return 0;
}

如果您不想为访问支付额外的运行时价格,则指向全局对象的指针或引用会很有用,因为模板实例化将使用常量(加载时解析)地址而不是间接地址来访问指定的对象就像使用常规指针或引用进行访问一样。 然而,付出的代价是每个对象都需要一个新的模板实例化,实际上很难想象在现实世界中这可能有用。

【讨论】:

    【解决方案3】:

    Performance TR 有几个示例,其中使用非类型模板来抽象硬件的访问方式(硬件内容从第 90 页开始;使用指针作为模板参数,例如,在第 113 页)。例如,注册的内存映射 I/O 将使用指向硬件区域的固定指针。虽然我自己从未使用过它(我只向 Jan Kristofferson 展示了如何使用它),但我很确定它用于开发一些嵌入式设备。

    【讨论】:

      【解决方案4】:

      通常使用指针模板参数来利用 SFINAE。如果您有两个无法使用 std::enable_if 默认参数的类似重载,这将特别有用,因为它们会导致重新定义错误。

      此代码会导致重新定义错误:

      template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
      void foo (T x)
      {
          cout << "integral"; 
      }
      
      template <typename T, typename = std::enable_if_t<std::is_floating_point<T>::value>>
      void foo (T x)
      {
          cout << "floating";
      }
      

      但是这段代码,它利用了有效的std::enable_if_t 构造默认折叠为void 的事实,很好:

                            // This will become void* = nullptr
      template <typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
      void foo (T x)
      {
          cout << "integral"; 
      }
      
      template <typename T, std::enable_if_t<std::is_floating_point<T>::value>* = nullptr>
      void foo (T x)
      {
          cout << "floating";
      }
      

      【讨论】:

        【解决方案5】:

        有时您需要提供一个具有特定签名的回调函数作为函数指针(例如void (*)(int)),但您要提供的函数采用不同(但兼容)参数(例如double my_callback(double x)),因此您可以'不要直接传递它的地址。此外,您可能需要在调用函数之前和之后做一些工作。

        编写一个隐藏函数指针的类模板很容易,然后从其operator()() 或其他一些成员函数内部调用它,但这并没有提供提取常规函数指针的方法,因为被调用的实体仍然需要this 指针才能找到回调函数。

        您可以通过构建一个适配器以优雅和类型安全的方式解决这个问题,给定一个输入函数,生成一个自定义的 static 成员函数(类似于常规函数,而不像非静态成员函数,可以获取其地址并用作函数指针)。 需要一个函数指针模板参数来将回调函数的知识嵌入到静态成员函数中。 The technique is demonstrated here.

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-03-30
          • 1970-01-01
          • 1970-01-01
          • 2016-08-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-04-12
          相关资源
          最近更新 更多