【问题标题】:C/C++ macro/template blackmagic to generate unique nameC/C++ 宏/模板 blackmagic 生成唯一名称
【发布时间】:2011-01-26 01:17:03
【问题描述】:

宏很好。 模板很好。 几乎任何工作都很好。

例子是OpenGL;但该技术是特定于 C++ 的,不依赖于 OpenGL 知识。

精确问题:

我想要一个表达式 E;我不必指定唯一名称;这样在定义 E 的地方调用构造函数,在块 E 结束的地方调用析构函数。

例如,考虑:

class GlTranslate {
  GLTranslate(float x, float y, float z); {
    glPushMatrix();
    glTranslatef(x, y, z);
  }
  ~GlTranslate() { glPopMatrix(); }
};

手动解决方案:

{
  GlTranslate foo(1.0, 0.0, 0.0); // I had to give it a name
  .....
} // auto popmatrix

现在,我不仅为 glTranslate 提供了这个,而且还有许多其他 PushAttrib/PopAttrib 调用。我宁愿不必为每个 var 想出一个唯一的名称。是否有一些涉及宏模板的技巧......或者其他会自动创建一个变量的东西,该变量的构造函数在定义点被调用;并在块结束时调用析构函数?

谢谢!

【问题讨论】:

  • 我不明白为什么想出一个唯一的名字比执行一些复杂的宏调用更难。
  • 为了它的价值,我曾尝试过类似的方案。我发现制作某种形式的 Transformation 类更容易,就像你有 push/pop 一样,使用调用翻译的成员函数等。然后你只有一个类,你也只是在你推需要。
  • 我认为答案是 LINECOUNTER :-)
  • @gman: 啊,好点子,因为我的方案中每个非首次翻译都会产生额外的 glPushMatrix()
  • @GMan:我建议(不是完全认真地)将块转换为单个逗号表达式。 AFAIK,在评估完整表达式后(在 work_to_be_done... 调用之后),临时对象被破坏 => 无需为实例命名,甚至不需要为实例命名;)

标签: c++ c-preprocessor raii


【解决方案1】:

我不会亲自这样做,只是想出独特的名字。但是如果你想这样做,一种方法是使用iffor的组合:

#define FOR_BLOCK(DECL) if(bool _c_ = false) ; else for(DECL;!_c_;_c_=true)

你可以像这样使用它

FOR_BLOCK(GlTranslate t(1.0, 0.0, 0.0)) {
  FOR_BLOCK(GlTranslate t(1.0, 1.0, 0.0)) {
    ...
  }
}

这些名称中的每一个都在不同的范围内,不会发生冲突。内部名称隐藏外部名称。 iffor 循环中的表达式是常量,编译器应该很容易优化。


如果你真的想传递一个表达式,你可以使用 ScopedGuard 技巧(参见Most Important const),但它需要更多的工作来编写它。但好的一面是,我们可以摆脱for 循环,让我们的对象评估为false

struct sbase { 
  operator bool() const { return false; } 
};

template<typename T>
struct scont : sbase { 
  scont(T const& t):t(t), dismiss() { 
    t.enter();
  }
  scont(scont const&o):t(o.t), dismiss() {
    o.dismiss = true;
  }
  ~scont() { if(!dismiss) t.leave(); }

  T t; 
  mutable bool dismiss;
};

template<typename T>
scont<T> make_scont(T const&t) { return scont<T>(t); }

#define FOR_BLOCK(E) if(sbase const& _b_ = make_scont(E)) ; else

然后您提供正确的enterleave 函数:

struct GlTranslate {
  GLTranslate(float x, float y, float z)
    :x(x),y(y),z(z) { }

  void enter() const {
    glPushMatrix();
    glTranslatef(x, y, z);
  }

  void leave() const {
    glPopMatrix();
  }

  float x, y, z;
};

现在你可以在用户端完全不写名字了:

FOR_BLOCK(GlTranslate(1.0, 0.0, 0.0)) {
  FOR_BLOCK(GlTranslate(1.0, 1.0, 0.0)) {
    ...
  }
}

如果你想一次传递多个表达式,这有点棘手,但你可以编写一个作用于operator, 的表达式模板,将所有表达式收集到一个scont 中。

template<typename Derived>
struct scoped_obj { 
  void enter() const { } 
  void leave() const { } 

  Derived const& get_obj() const {
    return static_cast<Derived const&>(*this);
  }
};

template<typename L, typename R> struct collect 
  : scoped_obj< collect<L, R> > {
  L l;
  R r;

  collect(L const& l, R const& r)
    :l(l), r(r) { }
  void enter() const { l.enter(); r.enter(); }
  void leave() const { r.leave(); l.leave(); }
};

template<typename D1, typename D2> 
collect<D1, D2> operator,(scoped_obj<D1> const& l, scoped_obj<D2> const& r) {
  return collect<D1, D2>(l.get_obj(), r.get_obj());
}

#define FOR_BLOCK(E) if(sbase const& _b_ = make_scont((E))) ; else

您需要从scoped_obj&lt;Class&gt; 继承RAII 对象,如下所示

struct GLTranslate : scoped_obj<GLTranslate> {
  GLTranslate(float x, float y, float z)
    :x(x),y(y),z(z) { }

  void enter() const {
    std::cout << "entering ("
              << x << " " << y << " " << z << ")" 
              << std::endl;
  }

  void leave() const {
    std::cout << "leaving ("
              << x << " " << y << " " << z << ")" 
              << std::endl;
  }

  float x, y, z;
};

int main() {
  // if more than one element is passed, wrap them in parentheses
  FOR_BLOCK((GLTranslate(10, 20, 30), GLTranslate(40, 50, 60))) {
    std::cout << "in block..." << std::endl;
  }
}

所有这些都不涉及虚函数,所涉及的函数对编译器是透明的。事实上,将上面的GLTranslate 改为将单个整数添加到全局变量中,然后再次减去它,以及下面定义的GLTranslateE,我做了一个测试:

// we will change this and see how the compiler reacts.
int j = 0;

// only add, don't subtract again
struct GLTranslateE : scoped_obj< GLTranslateE > {
  GLTranslateE(int x):x(x) { }

  void enter() const {
    j += x;
  }

  int x;
};

int main() {
  FOR_BLOCK((GLTranslate(10), GLTranslateE(5))) {
    /* empty */
  }
  return j;
}

事实上,优化级别的 GCC -O2 输出如下:

main:
    sub     $29, $29, 8
    ldw     $2, $0, j
    add     $2, $2, 5
    stw     $2, $0, j
.L1:
    add     $29, $29, 8
    jr      $31

我没想到,它优化得很好!

【讨论】:

  • @GMan 我从BOOST_FOREACH 宏中得到了这个诡计:)
  • @Johannes Schaub - litb:我感觉我错过了什么,但this 有什么问题。您的 if-for 构造如何更好?问题是这两个都允许我们在任何特定的范围内只使用一个对象,所以我们实际上需要多个不同的名称。我错过了什么?
  • @eSKay 您的链接代码没有问题。我的 if/for 构造扩展为与您的手动块等效的代码。但是,特别是对于我的最后一个解决方案,您不再需要名称,这是提问者最终想要实现的。就个人而言,我喜欢给那些反映其用途的对象命名。例如,将对象置于原点的翻译,我将使用Trans toOrigin(-x, -y, -z); 而不是只写Trans(-x, -y, -z)Trans foo(-x, -y, -z);,这不会传达转换的真正目的。
  • litb,你为外星人工作吗?你是三重代理还是什么?你每天早上都重新编译你的大脑吗?我感觉很糟糕。
  • Johannes,你的模板魔法总是给我疯狂的想法!感谢这个,尤其是最后一个,我会用它来做一些好事...... :)
【解决方案2】:

如果您的编译器支持__COUNTER__(可能支持),您可以尝试:

// boiler-plate
#define CONCATENATE_DETAIL(x, y) x##y
#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y)
#define MAKE_UNIQUE(x) CONCATENATE(x, __COUNTER__)

// per-transform type
#define GL_TRANSLATE_DETAIL(n, x, y, z) GlTranslate n(x, y, z)
#define GL_TRANSLATE(x, y, z) GL_TRANSLATE_DETAIL(MAKE_UNIQUE(_trans_), x, y, z)

对于

{
    GL_TRANSLATE(1.0, 0.0, 0.0);

    // becomes something like:
    GlTranslate _trans_1(1.0, 0.0, 0.0);

} // auto popmatrix

【讨论】:

  • @Magnus,GMan 可以使用_trans。这些名称仅保留在全局命名空间或 std 命名空间中。到处保留的名称是看起来像_Trans__trans 的名称。
  • 'LINE 可以和 'COUNTER 一样使用
  • __LINE__ 还具有您可以在宏中再次引用该变量的优点。使用__COUNTER__,它将再次增加。
  • @zneak:您可以通过注入另一个存储生成的变量名称的宏层来抵消这种情况。
  • @ThomasEding:你会怎么做?
【解决方案3】:

我认为现在可以做这样的事情:

struct GlTranslate
{
    operator()(double x,double y,double z, std::function<void()> f)
    {
        glPushMatrix(); glTranslatef(x, y, z);
        f();
        glPopMatrix();
    }
};

然后在代码中

GlTranslate(x, y, z,[&]()
{
// your code goes here
});

显然需要C++11

【讨论】:

  • 我喜欢它,您可以将 std::function 更改为模板以确保可以进行内联:-)
  • @EvanTeran 为什么在模板化的情况下可以内联?
  • std::function 使用类型擦除技术,防止编译器在即将生成 GLTranslate::operator() 函数时“看到”传递了什么函数。使用模板,它具有内联所需的所有信息,因为(有效地)它为每个传递的唯一函数发出自定义版本的函数,因此可以安全地内联它。
【解决方案4】:

一个答案中描述的规范方法是使用 lambda 表达式作为块,在 C++ 中您可以轻松编写模板函数

with<T>(T instance, const std::function<void(T)> &f) {
    f(instance);
}

并像使用它

with(GLTranslate(...), [] (auto translate) {
    ....
});

但是想要一种机制来避免在您的范围内定义名称的最常见原因是长函数/方法可以做很多事情。如果此类问题一直困扰着您,您可以尝试使用非常短的方法/函数来进行现代 OOP/简洁代码风格的更改 ?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多