(Ab) использование конструкторов и деструкторов для побочных эффектов плохая практика? Альтернативы?

Вот что я сделал, чтобы изменить заголовки в файле с разделителями табуляции. Я опускаю часть, где я только редактировал первую строку файла. Вы можете легко адаптировать его к Python с помощью библиотеки re. Это также включает разделение номеров (но сохраняет цифры вместе). Я сделал это за два шага, потому что это было проще, чем сказать, чтобы не подчеркивать в начале строки или вкладки.

Шаг первый ... найти заглавные буквы или целые числа, которым предшествуют строчные буквы, и перед ним обозначается символ подчеркивания:

Поиск:

([a-z]+)([A-Z]|[0-9]+)

Замена:

\1_\l\2/

Шаг второй ... возьмите вышеуказанное и снова запустите его конвертировать все кепки в нижний регистр:

Поиск:

([A-Z])

Замена (это обратная косая черта, нижний регистр L, обратная косая черта, одна):

\l\1
21
задан Thomas 22 July 2010 в 19:28
поделиться

10 ответов

Мне нравится идея использования 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 для восстановления безопасности исключений - если она вызывается в конструкторе и выбрасывает, деструктор не позвонят.

edit2: Общая техника -

Так что я еще немного поработал с этой идеей и придумал что-то получше:
Я определю с классом , который создает переменную контекста и вставляет ее в 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, []
{
    //...
});

Преимущества:

  1. Безопасность исключений обрабатывается auto_ptr теперь, поэтому, если действие сработает, контекст все равно будет уничтожен должным образом.
  2. Больше нет необходимости в методе Execute , так что он снова выглядит чистым! :)
  3. Ваши "контекстные" классы очень просты; вся логика обрабатывается классом With , поэтому вам просто нужно определить простой ctor / dtor для каждого нового типа контекста.

Одна мелочь: как я писал выше, вам нужно объявить ручные перегрузки для ctor для любого количества параметров, которое вам нужно; хотя даже один должен охватывать большинство случаев использования OpenGL, это не очень хорошо. Это должно быть аккуратно исправлено с помощью вариативных шаблонов - просто замените typename Arg в ctor на typename ... Args - но это будет зависеть от поддержки компилятором для этого (MSVC2010 не есть их еще).

24
ответ дан 29 November 2019 в 06:29
поделиться

Использование таких объектов называется RAII и очень типично для управления ресурсами. Да, иногда временные объекты уничтожаются слишком рано, потому что вы забыли указать переменное имя. Но у вас есть одно большое преимущество - код становится более безопасным и чистым от исключений - вам не нужно вызывать все средства очистки вручную для всех возможных путей кода.

Один совет: используйте разумные имена переменных, а не p. Назовите его matrixSwitcher или как-то так, чтобы читатели не думали, что это бесполезная переменная.

22
ответ дан sbi 29 November 2019 в 06:29
поделиться

Я хотел бы отметить, что мой ответ на самом деле содержит полезную информацию (более чем смутную ссылку на 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(...) {...} - прекрасное свойство для кода. Отзывы и др.

5
ответ дан Nordic Mainframe 29 November 2019 в 06:29
поделиться

Чтобы помочь вам понять, как долго программисты на с ++ занимались этим, я узнал об этой технике в конце 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
3
ответ дан Aaron Anodide 29 November 2019 в 06:29
поделиться

Я думаю, что это отличный идиоматический 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, даже если вы не знаете обертки наизусть.

0
ответ дан Fabio Fracassi 29 November 2019 в 06:29
поделиться

ScopeGuard приходит на ум. Обратите внимание, что в C ++ 0x шаблонах bind и variadic его можно переписать так, чтобы он был намного короче.

1
ответ дан Tomek 29 November 2019 в 06:29
поделиться

Как было отмечено другими, это хорошо известный и поощряемый паттерн в C++.

Способ справиться с проблемой забывания имени переменной - определить операции так, чтобы они нуждались в переменной. Либо сделав возможные действия членами класса RAII:

PushedMatrix pushed_matrix;;
pushed_matrix.transform( /*...*/ );

либо заставляя функции принимать класс RAII в качестве аргумента:

PushedMatrix pushed_matrix;
transform_matrix( pushed_matrix, /*...*/ );
6
ответ дан 29 November 2019 в 06:29
поделиться

Предупреждение: 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", так как вам будет легче увидеть, где вы забыли переменную.

4
ответ дан 29 November 2019 в 06:29
поделиться

Это типичный пример RAII. Недостатком этого метода является появление множества дополнительных классов. Для решения этой проблемы можно создать общий класс "guard", если это возможно. Есть и другая альтернатива: поднять библиотеку "Scope Exit" (http://www.boost.org/doc/libs/1_43_0/libs/scope_exit/doc/html/index.html). Вы можете попробовать ее, если, конечно, можете положиться на boost.

1
ответ дан 29 November 2019 в 06:29
поделиться

Я никогда не видел этого раньше, но должен признать, что это в некоторой степени здорово. Но я бы не стал его использовать, так как он не совсем интуитивно понятен.

EDIT: Как указал sharptooth, это называется RAII. В примере, который я нашел в Википедии, операции над ресурсом также обернуты в вызовы методов. В вашем примере это выглядело бы так:

WithPushedMatrix p;
p.setFLag(GL_BLEND);
p.doSomething();

Тогда понятно, что такое переменная, и другие разработчики получат интуицию, если прочитают ваш код. Конечно, тогда код OpenGL будет скрыт, но я думаю, что к этому быстро привыкаешь.

0
ответ дан 29 November 2019 в 06:29
поделиться