Вот что я сделал, чтобы изменить заголовки в файле с разделителями табуляции. Я опускаю часть, где я только редактировал первую строку файла. Вы можете легко адаптировать его к Python с помощью библиотеки re. Это также включает разделение номеров (но сохраняет цифры вместе). Я сделал это за два шага, потому что это было проще, чем сказать, чтобы не подчеркивать в начале строки или вкладки.
Шаг первый ... найти заглавные буквы или целые числа, которым предшествуют строчные буквы, и перед ним обозначается символ подчеркивания:
Поиск:
([a-z]+)([A-Z]|[0-9]+)
Замена:
\1_\l\2/
Шаг второй ... возьмите вышеуказанное и снова запустите его конвертировать все кепки в нижний регистр:
Поиск:
([A-Z])
Замена (это обратная косая черта, нижний регистр L, обратная косая черта, одна):
\l\1
Мне нравится идея использования RAII для управления состоянием OpenGL, но я бы пошел еще дальше: пусть конструктор класса WithFoo
принимает указатель на функцию в качестве параметра, который содержит код, который вы хотите выполнить в этом контексте. Затем не создавайте именованные переменные, а просто работайте с временными, передавая действие, которое вы хотите выполнить в этом контексте, как лямбда. (конечно, нужен C ++ 0x - тоже может работать с указателями на обычные функции, но это не так красиво.)
Примерно так: (отредактировано для восстановления безопасности исключений)
class WithPushedMatrix
{
public:
WithPushedMatrix()
{
glPushMatrix();
}
~WithPushedMatrix()
{
glPopMatrix();
}
template <typename Func>
void Execute(Func action)
{
action();
}
};
И используйте его так:
WithPushedMatrix().Execute([]
{
glBegin(GL_LINES);
//etc. etc.
});
Временный объект настроит ваше состояние, выполнит действие и затем автоматически отключит его; у вас нет «свободных» переменных состояния, плавающих вокруг, и действия, выполняемые в контексте, становятся прочно связанными с ним. Вы даже можете вложить несколько контекстных действий, не беспокоясь о порядке деструктора.
Вы даже можете пойти дальше и создать общий класс WithContext
, который принимает дополнительные параметры настройки и функции удаления.
edit : пришлось переместить вызов action ()
в отдельную функцию Execute
для восстановления безопасности исключений - если она вызывается в конструкторе и выбрасывает, деструктор не позвонят.
Так что я еще немного поработал с этой идеей и придумал что-то получше:
Я определю с классом
, который создает переменную контекста и вставляет ее в std :: auto_ptr
в ее инициализаторе, а затем вызывает действие
:
template <typename T>
class With
{
public:
template <typename Func>
With(Func action) : context(new T())
{ action(); }
template <typename Func, typename Arg>
With(Arg arg, Func action) : context(new T(arg))
{ action(); }
private:
const std::auto_ptr<T> context;
};
Теперь вы можете комбинировать его с типом контекста, который вы определили изначально:
struct PushedMatrix
{
PushedMatrix() { glPushMatrix(); }
~PushedMatrix() { glPopMatrix(); }
};
И использовать его следующим образом:
With<PushedMatrix>([]
{
glBegin(GL_LINES);
//etc. etc.
});
или
With<EnabledFlag>(GL_BLEND, []
{
//...
});
Преимущества:
auto_ptr
теперь, поэтому, если действие
сработает, контекст все равно будет уничтожен должным образом. Execute
, так что он снова выглядит чистым! :) With
, поэтому вам просто нужно определить простой ctor / dtor для каждого нового типа контекста. Одна мелочь: как я писал выше, вам нужно объявить ручные перегрузки для ctor для любого количества параметров, которое вам нужно; хотя даже один должен охватывать большинство случаев использования OpenGL, это не очень хорошо.
Это должно быть аккуратно исправлено с помощью вариативных шаблонов - просто замените typename Arg
в ctor на typename ... Args
- но это будет зависеть от поддержки компилятором для этого (MSVC2010 не есть их еще).
Использование таких объектов называется RAII и очень типично для управления ресурсами. Да, иногда временные объекты уничтожаются слишком рано, потому что вы забыли указать переменное имя. Но у вас есть одно большое преимущество - код становится более безопасным и чистым от исключений - вам не нужно вызывать все средства очистки вручную для всех возможных путей кода.
Один совет: используйте разумные имена переменных, а не p
. Назовите его matrixSwitcher
или как-то так, чтобы читатели не думали, что это бесполезная переменная.
Я хотел бы отметить, что мой ответ на самом деле содержит полезную информацию (более чем смутную ссылку на RAII, которая, по-видимому, стоит 19 голосов). Ему не нужен c ++ 0x для работы, он не является гипотетическим и исправляет проблемы ОП, связанные с необходимостью объявления переменной.
Есть очень хороший способ синтаксически увеличить конструкции RAII (или, точнее, ScopeGuards): оператор if () принимает объявления, которые находятся в пределах блока if :
#include <stdio.h>
class Lock
{
public:
Lock() { printf("locking\n"); }
~Lock() { printf("unlocking\n"); }
operator bool () const { return true;}
};
int main()
{
// id__ is valid in the if-block only
if (Lock id_=Lock()) {
printf("..action\n");
}
}
это печатает:
locking
..action
unlocking
Если мы добавим немного синтаксического сахара, мы можем написать
#define WITH(X) if (X with_id_=X())
int main()
{
WITH(Lock) {
printf("..action\n");
WITH(Lock) {
printf("more action\n");
}
}
}
И теперь мы используем тот факт, что временных которые используются для инициализации константной ссылки, остаются в живых до тех пор, пока константная ссылка остается в области действия , чтобы заставить ее работать с параметрами (мы также исправляем неприятность, что WITH (X) принимает завершающий остальное):
#include <stdio.h>
class ScopeGuard
{
public:
mutable int dummy;
operator bool () const { return false;}
ScopeGuard(){}
private:
ScopeGuard(const ScopeGuard &);
};
class Lock : public ScopeGuard
{
const char *s;
public:
Lock(const char *s_) : s(s_) { printf("locking %s\n",s); }
~Lock() { printf("unlocking %s\n",s); }
};
#define WITH(X) if (const ScopeGuard& with_id_=X) {} else
int main()
{
WITH(Lock("door")) {
printf("..action\n");
WITH(Lock("gate")) {
printf("more action\n");
}
}
}
TATA!
Приятным побочным эффектом этого метода является то, что все «защищенные» области единообразно идентифицируются с помощью шаблона WITH(...) {...}
- прекрасное свойство для кода. Отзывы и др.
Чтобы помочь вам понять, как долго программисты на с ++ занимались этим, я узнал об этой технике в конце 90-х, работая с COM.
Я думаю, что это личный выбор относительно того, какой именно механизм вы используете, чтобы использовать фундаментальные свойства фреймов стека и деструкторов c ++, чтобы упростить управление временем жизни вашего объекта. Я бы не стал уходить слишком далеко, чтобы избежать необходимости присваивать переменную.
(это следующая вещь, в которой я не уверен на 100%, но я надеюсь, что кто-то вмешается - я знаю, что делал это в прошлом, но я не смог найти это в Google сейчас и я пытался вспомнить ... видите, сборщики мусора притупили мой разум!)
Я верю, что вы можете заставить прицел с помощью простой старой пары кудряшек (POPOC).
{ // new stack frame
auto_ptr<C> instanceA(new C);
{
auto_ptr<C> instanceB(new C);
}
// instanceB is gone
}
// instanceA is gone
Я думаю, что это отличный идиоматический C ++. Недостатком является то, что вы в основном пишете (пользовательский) Wrapper вокруг библиотеки C OpenGL. Было бы замечательно, если бы существовала такая библиотека, может быть, что-то вроде (полу) официальной библиотеки OpenGL ++. Тем не менее, я написал код, подобный этому (из памяти), и был очень доволен им:
{
Lighting light = Light(Color(128,128,128));
light.pos(0.0, 1.0, 1.0);
Texture tex1 = Texture(GL_TEXTURE1);
tex1.set(Image("CoolTex.png"));
drawObject();
}
Затраты на написание оболочек не очень обременительны, и полученный код так же хорош, как рукописный код. И ИМХО гораздо легче читать, чем соответствующий код OpenGL, даже если вы не знаете обертки наизусть.
ScopeGuard приходит на ум. Обратите внимание, что в C ++ 0x шаблонах bind и variadic его можно переписать так, чтобы он был намного короче.
Как было отмечено другими, это хорошо известный и поощряемый паттерн в C++.
Способ справиться с проблемой забывания имени переменной - определить операции так, чтобы они нуждались в переменной. Либо сделав возможные действия членами класса RAII:
PushedMatrix pushed_matrix;;
pushed_matrix.transform( /*...*/ );
либо заставляя функции принимать класс RAII в качестве аргумента:
PushedMatrix pushed_matrix;
transform_matrix( pushed_matrix, /*...*/ );
Предупреждение: C ++ ориентированный на 0x ответ
Используемый вами шаблон - это RAII, и он широко используется для управления ресурсами. Единственная возможная альтернатива - использовать блоки try-catch, но обычно это делает ваш код слишком беспорядочным.
Теперь о проблемах. Во-первых, если вы не хотите кодировать разные классы для каждой комбинации функций OpenGL, есть еще одно преимущество C ++ 0x, заключающееся в том, что вы можете писать лямбда-функции и сохранять их в переменной. Итак, на вашем месте я бы создал такой класс:
template<typename Destr>
class MyCustom {
public:
template<typename T>
MyCustom(T onBuild, Destr onDestroy) :
_onDestroy(std::move(onDestroy))
{
onBuild();
}
~MyCustom() { _onDestroy(); }
private:
Destr _onDestroy;
};
template<typename T1, typename T2>
MyCustom<T2> buildCustom(T1 build, T2 destruct) { return MyCustom<T2>(std::move(build), std::move(destruct)); }
Тогда вы можете использовать его так:
auto matrixPushed = buildCustom([]() { glPushMatrix(); }, []() { glPopMatrix(); });
Или еще лучше:
auto matrixPushed = buildCustom(&glPushMatrix, &glPopMatrix);
Это также решило бы проблему «почему здесь эта бесполезная переменная? ", поскольку теперь его назначение становится очевидным.
Функция, переданная в конструктор, должна быть встроена, чтобы не снижать производительность. Деструктор должен храниться как указатель на функцию, поскольку лямбда-функции без каких-либо элементов внутри скобок [] должны быть реализованы как простые функции (согласно стандартам).
Ваша проблема "мгновенно уничтоженная переменная" также может быть частично решена с помощью "buildCustom", так как вам будет легче увидеть, где вы забыли переменную.
Это типичный пример RAII. Недостатком этого метода является появление множества дополнительных классов. Для решения этой проблемы можно создать общий класс "guard", если это возможно. Есть и другая альтернатива: поднять библиотеку "Scope Exit" (http://www.boost.org/doc/libs/1_43_0/libs/scope_exit/doc/html/index.html). Вы можете попробовать ее, если, конечно, можете положиться на boost.
Я никогда не видел этого раньше, но должен признать, что это в некоторой степени здорово. Но я бы не стал его использовать, так как он не совсем интуитивно понятен.
EDIT: Как указал sharptooth, это называется RAII. В примере, который я нашел в Википедии, операции над ресурсом также обернуты в вызовы методов. В вашем примере это выглядело бы так:
WithPushedMatrix p;
p.setFLag(GL_BLEND);
p.doSomething();
Тогда понятно, что такое переменная, и другие разработчики получат интуицию, если прочитают ваш код. Конечно, тогда код OpenGL будет скрыт, но я думаю, что к этому быстро привыкаешь.