10

我想定义一个可以在不同位置(在文件范围内)调用的宏,以便创建执行某些操作的函数。(在下面的示例中,函数只是打印一条消息,但当然我的真正意图是做一些其他有用的事情。)挑战是我想要一些“管理器”功能(在我的示例中它只是main()) 以某种方式成功地使它们全部被调用(以任何顺序),而没有任何代码依赖于宏调用(当然,宏调用本身除外)。我的意思是,一旦文件被写入,另一个程序员将​​能够在不同的地方插入一些新的宏调用或删除一些现有的调用,并且代码仍然可以工作而无需任何进一步的更改。我意识到这可以使用静态对象来完成,但我想探索一种不同的方法。我将使用一些模板技巧和__LINE__单调递增的事实。

#include <iostream>
using namespace std;

template<int i>
inline void f()
{
   f<i-1>();
}

#define START_REGISTRATION                                \
template<>                                                \
inline void f<__LINE__>() {}  /* stop the recursion */    \
template<> void f<__LINE__>()  /* force semicolon */

#define REGISTER(msg)                                     \
template<>                                                \
inline void f<__LINE__>()                                 \
{                                                         \
   cout << #msg << endl;                                  \
   f<__LINE__ - 1>();                                     \
}                                                         \
template<> void f<__LINE__>()  /* force semicolon */

// Unrelated code ...

START_REGISTRATION;

// Unrelated code ...

REGISTER(message 1);

// Unrelated code ...

REGISTER(message 2);

// Unrelated code ...

REGISTER(message 3);

// Unrelated code ...

// manager function (in this case main() )
int main()
{
   f<__LINE__>();
}

这打印

message 3
message 2
message 1

正如预期的那样。

该解决方案有一些缺点。

  1. 不能REGISTER在同一行调用两次。
  2. #line如果被玩会坏掉。
  3. 在所有调用REGISTER.
  4. 由于递归实例化而增加了编译时间。
  5. 除非所有的“虚拟”实例f都被内联,否则运行时的调用堆栈深度将与管理器之间START_REGISTRATION;f<__LINE__>();管理器中的行数一样大。
  6. 代码膨胀:除非所有的“虚拟”实例f都被内联,否则实例的数量同样会很大。
  7. 过度的实例化递归深度可能会达到编译器的限制(在我的系统上默认为 500)。

问题1-4 我真的不介意。问题 5 可以通过让每个函数返回一个指向前一个函数的指针来消除,并让管理器使用这些指针迭代地调用函数,而不是让它们相互调用。可以通过创建一个类似的类模板构造来消除问题 6,该构造能够为每次调用REGISTER在上一次调用中实例化了什么函数,因此只实例化实际执行某些操作的函数。过多的实例化将从函数模板转移到类模板,但类实例化只会对编译器造成负担;他们不会触发任何代码生成。所以我真正关心的是问题 7,问题是:有没有办法重组事物,以便编译器迭代而不是递归地进行实例化。我也完全接受不同的方法(除了那些涉及静态对象的方法)。一个简单的解决方案是在经理之前将所有注册组合在一起(或添加一个STOP_REGISTRATION宏来结束注册块)但这会破坏我的目的的重要部分(在定义它的代码旁边注册东西)。

编辑: 有一些有趣的建议,但恐怕我并没有明确说明我希望实现的目标。我对两件事真的很感兴趣:解决提出的问题(即,没有静态,每个注册单行,添加/删除注册时没有额外的更改,虽然我没有这么说,但只有标准 C++ --- 因此,没有提升)。正如我在下面的评论中所说,我的兴趣更多是理论上的:我希望学习一些新技术。因此,我真的很想专注于重组事物,以便消除(或至少减少)递归,或者找到满足我上面列出的约束的不同方法。

编辑 2: MSalter 的解决方案向前迈出了一大步。起初我以为每次注册都会产生排队的全部成本,但后来我意识到一个函数当然只能实例化一次,所以在实例化方面我们付出与线性搜索相同的代价,但是递归深度变为对数。如果我能解决它,我将发布一个完整的解决方案,消除问题 5-7。不过,看看它是否可以在恒定的递归深度中完成,最好的情况是,实例化的数量与调用的数量成线性关系(a-la 提升解决方案)。

编辑3: 这是完整的解决方案。

#define START_REGISTRATION                                          \
template<int lo, int hi>                                            \
struct LastReg {                                                    \
  enum {                                                            \
     LINE_NUM = LastReg<(lo + hi)/2 + 1, hi>::LINE_NUM ?            \
        static_cast<int>(LastReg<(lo + hi)/2 + 1, hi>::LINE_NUM) :  \
        static_cast<int>(LastReg<lo, (lo + hi)/2>::LINE_NUM)        \
  };                                                                \
};                                                                  \
template<int l>                                                     \
struct LastReg<l, l> {                                              \
   enum { LINE_NUM = 0 };                                           \
};                                                                  \
template<int l>                                                     \
struct PrevReg {                                                    \
   enum { LINE_NUM = LastReg<__LINE__ + 1, l - 1>::LINE_NUM };      \
};                                                                  \
template<int l> void Register() {}                                  \
template<int l> void Register()  /* force semicolon */


#define REGISTER(msg)                                               \
template<>                                                          \
struct LastReg<__LINE__, __LINE__> {                                \
   enum { LINE_NUM = __LINE__ };                                    \
};                                                                  \
template<>                                                          \
void Register<__LINE__>()                                           \
{                                                                   \
   cout << __LINE__ << ":" << #msg << endl;                         \
   Register<PrevReg<__LINE__>::LINE_NUM>();                         \
}                                                                   \
template<> void Register<__LINE__>()  /* force semicolon */


#define END_REGISTRATION                                            \
void RegisterAll()                                                  \
{                                                                   \
   Register<PrevReg<__LINE__>::LINE_NUM>();                         \
}                                                                   \
void RegisterAll()  /* force semicolon */


START_REGISTRATION;

REGISTER(message 1);

REGISTER(message 2);

END_REGISTRATION;


int main()
{
   RegisterAll();
}
4

4 回答 4

5

您面临的问题是您正在对 进行线性搜索f<i>,这会导致过多的实例化。

解决方案是让f<i>call g<i,0>。这依次调用g<i,i/2>和,g<i/2,0>它调用g<i,i/2+i/4>g<i/2+i/4,i/2>和ectetera。你当然会专攻里面。g<i/2,i/4>g<i/4, 0>g<__LINE__, __LINE__>REGISTER()

实例化f<65536>仍会导致 65536 个模板实例化(您实际上是在检查所有之前的 65536 行),但递归深度限制为 log(65536),或 16 个级别。这是可行的。

于 2011-05-27T13:51:26.270 回答
1

也许是这样的:

template<typename T>
struct register_struct {
    virtual void operator()() = 0;
    register_struct();
    register_struct *pNext;
};

template<typename T>
struct registry {
    static register_struct<T> *chain;
    static void walk() {
        register_struct<T> *p = chain;
        while (p) {
            (*p)();
            p = p->pNext;
        }
    }
};

template<typename T>
register_struct<T> *registry<T>::chain = NULL;

template<typename T>
register_struct<T>::register_struct()
{
    pNext = registry<T>::chain;
    registry<T>::chain = this;
}

#define DECL_REGISTRY(name) \
    struct tag_##name { } ; \
    void name() { registry<tag_##name>::walk(); }

#define JOIN_EXPAND(x, y) JOIN_EXPAND_2(x, y)
#define JOIN_EXPAND_2(x, y) x ## y

// Invoke REGISTER_PRINT at file scope!
#define REGISTER_PRINT(name, text) \
    namespace { \
    static struct : public register_struct<tag_##name> { \
        void operator()() { \
            std::cout << text << std::endl; \
        } \
    } JOIN_EXPAND(rs_##name##_, __LINE__); \
    }

DECL_REGISTRY(foo);

REGISTER_PRINT(foo, "hello")
REGISTER_PRINT(foo, "world")

int main() {
    foo();
    return 0;
}

这将解决递归实例化问题,但您仍然限于每行一个注册。然而,由于注册是在文件范围内,这应该(希望!)不是一个问题。

请注意,如果您希望在此之前调用它们,则使用它是不安全的main()- 您必须在使用它之前允许静态构造函数完成。

于 2011-05-27T14:08:35.350 回答
0

这是一个将递归限制为实际注册的函数数量的解决方案。__LINE__我没有使用 id 作为 id,而是使用,BOOST_PP_COUNTER这是预处理器可用的递增计数器。

诀窍是您不能在宏内增加计数器,因为增加是通过包含头文件完成的。因此,我也不得不依赖文件包含,因此需要两行而不是一行来注册消息(一行定义消息,一行实际注册它)。

这是代码:

//register.hpp

#include <boost/preprocessor/slot/counter.hpp>

// general template function, not defined
template <unsigned int ID>
void f();

// base case, to stop recursion
template <>
void f<0>() {}


// macro to "hide" the name of the header to include (which should be in a
// "hidden" folder like "detail" in Boost 
#define REGISTER() "actually_register_msg.hpp"

//actually_register_msg.hpp

#include <boost/preprocessor/stringize.hpp>

// increment the counter
#include BOOST_PP_UPDATE_COUNTER()

template<>
inline void f< BOOST_PP_COUNTER >()
{
    std::cout << BOOST_PP_STRINGIZE( MSG_TO_REGISTER ) << std::endl;
    f< BOOST_PP_COUNTER - 1 >(); // call previously registered function
}

// to avoid warning and registering multiple times the same message
#undef MSG_TO_REGISTER

// id of the last registered function
#define LAST_FUNCTION_ID BOOST_PP_COUNTER

// main.cpp

#define MSG_TO_REGISTER message 1
#include REGISTER()

#define MSG_TO_REGISTER message 2
#include REGISTER()

#define MSG_TO_REGISTER message 3
#include REGISTER()

int main()
{
    f< LAST_FUNCTION_ID >();
}

像您的代码一样,这会打印

message 3
message 2
message 1

当然,注册调用不太漂亮(一个#define和一个#include而不是单个宏调用),但是您避免了很多不必要的模板实例化。

于 2011-05-27T16:34:00.887 回答
0

我曾经做过类似的事情,它只实例化了有限数量的专业化。目标是将所有特化聚合到一个指针数组中,并通过枚举使用单个方法访问它们,但您可以轻松地将其调整为类似(如我所想)的需求。

#include <iostream>

using namespace std;

class I {
public:
    virtual ~I() {};

    virtual void P(int index) = 0;

    enum Item {
        Item0,
        Item1,
        Item2,
        Item3,
        Item4,
        ItemNum
    };
};


template <class T> class A: public I {
public:
    A() {
        Unroll<A<T>, ItemNum> tmp (m_F);
    }
    virtual ~A() {
    }

    void P(int index) { (this->*m_F[index])(); }

protected:
    typedef void (A<T>::*F)();
    F m_F[ItemNum];

    template <int N> void p() { cout << "default!" << endl; }

    template <class W, int C> struct Unroll
    {
        Unroll(typename W::F * dest)
        {
            dest[C-1] = & W::template p<C-1>;
            Unroll<W, C-1> u(dest);
        }
    };
};

template <class T> template <class W> struct A<T>::Unroll<W, 0>
{ public: Unroll(typename W::F * dest) {} };

class B: public A<B>
{
public:

};

template <> template <> void A<B>::p<A<B>::Item1>() { cout << 1 << endl; }
template <> template <> void A<B>::p<A<B>::Item2>() { cout << 2 << endl; }
template <> template <> void A<B>::p<A<B>::Item4>() { cout << "it hacking works!" << endl; }


int main()
{
    I *a = new B;
    for (int i = 0; i < I::ItemNum; ++i) a->P(i);
    return 0;
}
于 2011-05-27T14:53:08.340 回答