Я бы не стал делать это лично, а просто придумал бы уникальные имена. Но если вы хотите это сделать, одним из способов является использование комбинации if
и for
:
#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
Затем вы предоставляете правильные функции 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)) {
...
}
}
Если вы хотите передать несколько выражений одновременно, это немного сложнее, но вы можете написать шаблон выражения, который действует на 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
Вам необходимо унаследовать объект RAII от scoped_obj<Class>
, как показано ниже
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
Я бы этого не ожидал, он довольно хорошо оптимизирован!