18

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

功能演示(用于指针):

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

任何启示将不胜感激!

4

5 回答 5

13

指向函数的指针

指向成员函数的指针和指向函数的非类型参数对某些委托非常有用。它使您可以制作非常快速的代表。

前任:

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

现场示例在这里

指向数据的指针

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

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

例子:

#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_ptrCodepadshared_ptr和 Ideone 上均不可用。 活生生的例子

于 2012-11-04T17:40:48.920 回答
8

指向成员的指针的情况与指向数据或引用的指针有很大不同。

如果您想指定要调用的成员函数(或要访问的数据成员)但不想将对象放在特定的层次结构中(否则虚拟方法通常就足够了),则指向成员的指针作为模板参数会很有用.

例如:

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

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

于 2012-11-04T17:49:12.457 回答
4

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

于 2012-11-04T17:41:55.493 回答
4

It is common to use pointer template arguments to leverage SFINAE. This is especially useful if you have two similar overloads which you couldn't use std::enable_if default arguments for, as they would cause a redefinition error.

This code would cause a redefinition error:

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

But this code, which utilises the fact that valid std::enable_if_t constructs collapse to void by default, is fine:

                      // 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";
}
于 2015-06-17T09:37:46.420 回答
2

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

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

您可以通过构建一个适配器以优雅和类型安全的方式解决这个问题,给定一个输入函数,生成一个自定义的静态成员函数(与常规函数不同,与非静态成员函数不同,它可以获取其地址并用于函数指针)。 需要一个函数指针模板参数来将回调函数的知识嵌入到静态成员函数中。 此处演示了该技术。

于 2012-11-04T18:27:43.140 回答