Лучшая практика для справочной идиомы Scoped? - PullRequest
2 голосов
/ 24 марта 2009

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

Мы используем объекты с пересчетом, где у нас есть объекты AddRef () и Release () для этих классов. Один конкретный объект является производным от объекта ref-count, но общая функция для получения экземпляра этих объектов (GetExisting) скрывает AddRef () внутри себя без объявления о том, что она это делает. Это требует выполнения Release в конце функционального блока, чтобы освободить скрытые ссылки, но разработчик, который не проверял реализацию GetExisting (), не знал бы об этом, и тот, кто забыл добавить Release в конце функция (скажем, во время безумной поры времени исправления ошибок) пропускает объекты. Это, конечно, был мой ожог.


void SomeFunction(ProgramStateInfo *P)
{
   ThreadClass *thread = ThreadClass::GetExisting( P );
   // some code goes here
   bool result = UseThreadSomehow(thread);
   // some code goes here
   thread->Release();  // Need to do this because GetExisting() calls AddRef()
}

Итак, я написал небольшой класс, чтобы избежать необходимости в Release () в конце этих функций.


class ThreadContainer
{
private:
    ThreadClass *m_T;
public:
    ThreadContainer(Thread *T){ m_T = T; }
    ~ThreadContainer() { if(m_T) m_T->Release(); }
    ThreadClass * Thread() const { return m_T; }
};

Так что теперь я могу просто сделать это:


void SomeFunction(ProgramStateInfo *P)
{
   ThreadContainer ThreadC(ThreadClass::GetExisting( P ));
   // some code goes here
   bool result = UseThreadSomehow(ThreadC.Thread());
   // some code goes here
   // Automagic Release() in ThreadC Destructor!!!
}

Что мне не нравится, так это то, что для доступа к указателю потока мне нужно вызвать функцию-член ThreadContainer, Thread (). Есть ли какой-нибудь умный способ, которым я могу это очистить, чтобы он стал синтаксически красивее, или что-то подобное затеняет смысл контейнера и создает новые проблемы для разработчиков, незнакомых с кодом?

Спасибо.

Ответы [ 5 ]

10 голосов
/ 24 марта 2009

используйте boost :: shared_ptr можно определить вашу собственную функцию деструктора, например, в следующем примере: http://www.boost.org/doc/libs/1_38_0/libs/smart_ptr/sp_techniques.html#com

7 голосов
/ 24 марта 2009

Да, вы можете реализовать operator ->() для класса, который будет рекурсивно вызывать operator ->() для всего, что вы возвращаете:

class ThreadContainer
{
private:
    ThreadClass *m_T;
public:
    ThreadContainer(Thread *T){ m_T = T; }
    ~ThreadContainer() { if(m_T) m_T->Release(); }
    ThreadClass * operator -> () const { return m_T; }
};

Это эффективно использует семантику умного указателя для вашего класса оболочки:

Thread *t =  new Thread();
...
ThreadContainer tc(t);
...
tc->SomeThreadFunction(); // invokes tc->t->SomeThreadFunction() behind the scenes...

Вы также можете написать функцию преобразования, чтобы аналогичным образом разрешать вызовы типа UseThreadSomehow(ThreadContainer tc).

Если опция Boost является опцией, я думаю, вы можете настроить shared_ptr, чтобы он также действовал как интеллектуальный эталон.

4 голосов
/ 24 марта 2009

Взгляните на ScopeGuard . Он допускает такой синтаксис (бесстыдно украденный по этой ссылке):

{
    FILE* topSecret = fopen("cia.txt");
    ON_BLOCK_EXIT(std::fclose, topSecret);
    ... use topSecret ...
} // topSecret automagically closed

Или вы можете попробовать Boost :: ScopeExit :

void World::addPerson(Person const& aPerson) {
    bool commit = false;
    m_persons.push_back(aPerson);  // (1) direct action
    BOOST_SCOPE_EXIT( (&commit)(&m_persons) )
    {
        if(!commit)
            m_persons.pop_back(); // (2) rollback action
    } BOOST_SCOPE_EXIT_END

    // ...                        // (3) other operations

    commit = true;                // (4) turn all rollback actions into no-op
}
2 голосов
/ 24 марта 2009

Я бы порекомендовал следовать советам bb и использовать boost :: shared_ptr <>. Если повышение не является опцией, вы можете взглянуть на std :: auto_ptr <>, который прост и, вероятно, отвечает большинству ваших потребностей. Имейте в виду, что std :: auto_ptr имеет специальную семантику перемещения, которую вы, вероятно, не хотите имитировать.

Подход предусматривает предоставление операторов * и -> вместе с геттером (для необработанного указателя) и операцией освобождения на случай, если вы хотите освободить контроль над внутренним объектом.

1 голос
/ 24 марта 2009

Вы можете добавить автоматический оператор приведения типа для возврата вашего необработанного указателя. Этот подход используется классом Microsoft CString, чтобы обеспечить легкий доступ к базовому символьному буферу, и я всегда находил его удобным. С этим методом могут быть обнаружены некоторые неприятные сюрпризы, так как в любое время у вас есть неявное преобразование, но я не сталкивался ни с одним.

class ThreadContainer
{
private:
    ThreadClass *m_T;
public:
    ThreadContainer(Thread *T){ m_T = T; }
    ~ThreadContainer() { if(m_T) m_T->Release(); }
    operator ThreadClass *() const { return m_T; }
};

void SomeFunction(ProgramStateInfo *P)
{
   ThreadContainer ThreadC(ThreadClass::GetExisting( P ));
   // some code goes here
   bool result = UseThreadSomehow(ThreadC);
   // some code goes here
   // Automagic Release() in ThreadC Destructor!!!
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...