C/C++宏/模板 blackmagic 生成唯一的名称

发布于 2024-08-24 05:54:49 字数 643 浏览 5 评论 0原文

宏就好了。 模板很好。 几乎无论它起作用什么都很好。

例子是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 调用。我不想为每个变量起一个唯一的名称。是否有一些涉及宏模板的技巧......或者其他东西会自动创建一个变量,该变量的构造函数在定义时被调用;并在块末尾调用析构函数?

谢谢!

Macros are fine.
Templates are fine.
Pretty much whatever it works is fine.

The example is OpenGL; but the technique is C++ specific and relies on no knowledge of OpenGL.

Precise problem:

I want an expression E; where I do not have to specify a unique name; such that a constructor is called where E is defined, and a destructor is called where the block E is in ends.

For example, consider:

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

Manual solution:

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

Now, I have this not only for glTranslate, but lots of other PushAttrib/PopAttrib calls too. I would prefer not to have to come up with a unique name for each var. Is there some trick involving macros templates ... or something else that will automatically create a variable who's constructor is called at point of definition; and destructor called at end of block?

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

dawn曙光 2024-08-31 05:54:49

我个人不会这样做,只是想出独特的名字。但如果你想这样做,一种方法是使用 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 技巧(参见 最重要的 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

然后您提供正确的 enter > 和 leave 函数:

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)) {
    ...
  }
}

如果您想一次传递多个表达式,则有点棘手,但您可以编写一个表达式模板作用于运算符,将所有表达式收集到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 继承 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;
}

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

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

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

I would not do this personally but just come up with unique names. But if you want to do it, one way is to use a combination of if and for:

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

You can use it like

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

Each of those names are in separate scopes and won't conflict. The inner names hide the outer names. The expressions in the if and for loops are constant and should be easily optimized by the compiler.


If you really want to pass an expression, you can use the ScopedGuard trick (see Most Important const), but it will need some more work to write it. But the nice side is, that we can get rid of the for loop, and let our object evaluate to 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

You then provide the proper enter and leave functions:

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

Now you can write it entirely without a name on the user side:

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

If you want to pass multiple expressions at once, it's a bit more tricky, but you can write an expression template that acts on operator, to collect all expressions into a 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

You need to inherit the RAII object from scoped_obj<Class> like the following shows

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

All of these involve no virtual functions, and the functions involved are transparent to the compiler. In fact, with the above GLTranslate changed to add a single integer to a global variable and when leaving subtracting it again, and the below defined GLTranslateE, i did a test:

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

In fact, GCC at optimization level -O2 outputs this:

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

I wouldn't have expected that, it optimized quite well!

云巢 2024-08-31 05:54:49

如果您的编译器支持 __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

If your compiler supports __COUNTER__ (it probably does), you could try:

// 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)

For

{
    GL_TRANSLATE(1.0, 0.0, 0.0);

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

} // auto popmatrix
命硬 2024-08-31 05:54:49

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

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

I think it's now possible to do something like this:

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

then in the code

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

Obviously, C++11 is needed

命硬 2024-08-31 05:54:49

使用 C++17,一个非常简单的宏,导致直观的用法:

#define given(...) if (__VA_ARGS__; true)

并且可以嵌套:

given (GlTranslate foo(1.0, 0.0, 0.0))
{
    foo.stuff();

    given (GlTranslate foo(1.0, 2.0, 3.0))
    {
        foo.stuff();
        ...
    }
}

Using C++17, a very simple macro leading to an intuitive usage:

#define given(...) if (__VA_ARGS__; true)

And can be nested:

given (GlTranslate foo(1.0, 0.0, 0.0))
{
    foo.stuff();

    given (GlTranslate foo(1.0, 2.0, 3.0))
    {
        foo.stuff();
        ...
    }
}
久随 2024-08-31 05:54:49

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

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

并像使用它一样

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

,但需要一种避免在作用域中定义名称的机制的最常见原因是长函数/ 可以做很多事情的方法。如果这种问题一直困扰着您,您可以尝试采用现代 OOP / 干净代码风格,使用非常短的方法/函数进行更改

The canonical way as described in one answer is to use a lambda expression as the block, in C++ you can easily write a template function

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

and use it like

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

but the most common reason for wanting a mechanism for avoiding defining names in your scope are long functions / methods that do lots of things. You might try a modern OOP / clean code inspired style with very short methods / functions for a change if this kind of problem keeps bothering you ????

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文