Макрос/шаблон C/C++ blackmagic для генерации уникального имени

Макросы прекрасны. Шаблоны прекрасны. В значительной степени независимо от того, что это работает, прекрасен.

Примером является 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. Есть ли некоторый прием, включающий шаблоны макросов... или что-то еще, что автоматически создаст переменную, кто конструктор, назван в точке определения; и деструктор, названный в конце блока?

Спасибо!

40
задан Walter 19 September 2015 в 16:29
поделиться

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)

For

{
    GL_TRANSLATE(1.0, 0.0, 0.0);

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

} // auto popmatrix
36
ответ дан 27 November 2019 в 01:13
поделиться

Я бы не стал делать это лично, просто придумал бы уникальные имена. Но если вы хотите это сделать, один из способов - использовать комбинацию if и для :

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

Каждое из этих имен находится отдельно объемах и не будет конфликтовать. Внутренние имена скрывают внешние имена. Выражения в циклах if и for являются постоянными и должны быть легко оптимизированы компилятором.


Если вы действительно хотите передать выражение, вы можете использовать трюк 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

Затем вы вводите правильный ввод ] и 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;
};

Теперь вы можете написать его полностью без имени на стороне пользователя:

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

Если вы хотите передать несколько выражений одновременно, это немного сложнее, но вы можете напишите шаблон выражения, который действует на оператор , , чтобы собрать все выражения в scont 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

Вам необходимо унаследовать объект RAII от scoped_obj , как показано ниже.

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

Я не ожидал этого, он оптимизировался довольно хорошо!

62
ответ дан 27 November 2019 в 01:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: