Вопрос на интеллектуальных указателях и их неизбежном индетерминизме

Это, вероятно, не актуально. Но для устранения этих html-entites из целого документа вы можете сделать что-то вроде этого: (предположим, что document = page и прошу простить неаккуратный код, но если у вас есть идеи о том, как сделать его лучше, Im all ears - Im new to это).

import re
import HTMLParser

regexp = "&.+?;" 
list_of_html = re.findall(regexp, page) #finds all html entites in page
for e in list_of_html:
    h = HTMLParser.HTMLParser()
    unescaped = h.unescape(e) #finds the unescaped value of the html entity
    page = page.replace(e, unescaped) #replaces html entity with unescaped value
16
задан Ash 31 December 2008 в 15:18
поделиться

9 ответов

Проблемная Сводка

Там является двумя конкурирующими проблемами в этом вопросе.

  1. управление жизненным циклом Subsystem с, позволяя их удаление в нужное время.
  2. Клиенты Subsystem с должна знать, что Subsystem они используют, допустимо.

Обработка № 1

System владеет Subsystem с и должна управлять их жизненным циклом со своим собственным объемом. Используя shared_ptr с для этого особенно полезна, поскольку она упрощает разрушение, но Вы не должны раздавать их, потому что тогда Вы освобождаете детерминизм, который Вы ищете относительно их освобождения.

Обработка № 2

Это - больше беспокойства межжала для обращения. Описывая проблему более подробно, Вам нужны клиенты для получения объекта, который ведет себя как Subsystem, в то время как это Subsystem (и это - родитель System) существует, но ведет себя соответственно после Subsystem уничтожается.

Это легко решено комбинацией Шаблон "proxy" , Шаблон состояния и Шаблон Несуществующего объекта . В то время как это, может казаться, немного сложно решения, ' существует простота только, чтобы иметься с другой стороны сложности '. Как разработчики Библиотеки/API, мы должны пойти дополнительная миля для создания наших систем устойчивыми. Далее, мы хотим, чтобы наши системы вели себя интуитивно, как пользователь ожидает, и затухать корректно, когда они попытаются неправильно использовать их. Существует много решений этой проблемы, однако, этот должен получить Вас к тому всему важному моменту, где, как Вы и Scott Meyers говорит, это" просто в использовании правильно и трудно использовать неправильно. '

Теперь, я предполагаю, что в действительности, System соглашения в некотором базовом классе [1 113] с, из которой Вы происходите всевозможный Subsystem с. Я представил его ниже как [1 115]. Необходимо представить Прокси объект, SubsystemProxy ниже, который реализует интерфейс [1 117] запросами на переадресацию к объекту, который это проксирует. (В этом смысле это очень похоже на приложение особого назначения Шаблон "декоратор" .) Каждый Subsystem создает один из этих объектов, которые он содержит через shared_ptr и возвращает, когда требуется через [1 120], который называет родитель System объект, когда GetSubsystem() назван.

, Когда System выходит из объема, каждого из, его Subsystem, объекты разрушены. В их деструкторе они звонят mProxy->Nullify(), который вызывает их Прокси объекты изменить их состояние . Они делают это путем изменения на точку к Несуществующий объект , который реализует эти SubsystemBase интерфейс, но делает так, ничего не делая.

Используя Шаблон состояния здесь позволил клиентскому приложению абсолютно не обращать внимания на то, существует ли деталь Subsystem. Кроме того, это не должно проверять указатели или иметь в наличии экземпляры, которые должны были быть уничтожены.

Шаблон "proxy" позволяет клиенту зависеть от объекта легкого веса, который полностью оборачивает детали внутренних работ API и поддерживает постоянный, универсальный интерфейс.

Шаблон Несуществующего объекта позволяет Прокси функционировать после того, как оригинал Subsystem был удален.

Пример кода

я поместил грубый качественный пример псевдокода сюда, но я не был удовлетворен им. Я переписал его, чтобы быть точным, компилируя (я использовал g ++), пример того, что я описал выше. Чтобы заставить его работать, я должен был представить несколько других классов, но их использование должно быть четким из их имен. Я использовал Шаблон "одиночка" для NullSubsystem класс, поскольку это имеет смысл, что Вам не был бы нужен больше чем один. ProxyableSubsystemBase полностью абстрагирует поведение Проксирования далеко от эти Subsystem, позволяя ему быть незнакомым с этим поведением. Вот диаграмма UML классов:

UML Diagram of Subsystem and System Hierarchy

Пример кода:

#include <iostream>
#include <string>
#include <vector>

#include <boost/shared_ptr.hpp>


// Forward Declarations to allow friending
class System;
class ProxyableSubsystemBase;

// Base defining the interface for Subsystems
class SubsystemBase
{
  public:
    // pure virtual functions
    virtual void DoSomething(void) = 0;
    virtual int GetSize(void) = 0;

    virtual ~SubsystemBase() {} // virtual destructor for base class
};


// Null Object Pattern: an object which implements the interface to do nothing.
class NullSubsystem : public SubsystemBase
{
  public:
    // implements pure virtual functions from SubsystemBase to do nothing.
    void DoSomething(void) { }
    int GetSize(void) { return -1; }

    // Singleton Pattern: We only ever need one NullSubsystem, so we'll enforce that
    static NullSubsystem *instance()
    {
      static NullSubsystem singletonInstance;
      return &singletonInstance;
    }

  private:
    NullSubsystem() {}  // private constructor to inforce Singleton Pattern
};


// Proxy Pattern: An object that takes the place of another to provide better
//   control over the uses of that object
class SubsystemProxy : public SubsystemBase
{
  friend class ProxyableSubsystemBase;

  public:
    SubsystemProxy(SubsystemBase *ProxiedSubsystem)
      : mProxied(ProxiedSubsystem)
      {
      }

    // implements pure virtual functions from SubsystemBase to forward to mProxied
    void DoSomething(void) { mProxied->DoSomething(); }
    int  GetSize(void) { return mProxied->GetSize(); }

  protected:
    // State Pattern: the initial state of the SubsystemProxy is to point to a
    //  valid SubsytemBase, which is passed into the constructor.  Calling Nullify()
    //  causes a change in the internal state to point to a NullSubsystem, which allows
    //  the proxy to still perform correctly, despite the Subsystem going out of scope.
    void Nullify()
    {
        mProxied=NullSubsystem::instance();
    }

  private:
      SubsystemBase *mProxied;
};


// A Base for real Subsystems to add the Proxying behavior
class ProxyableSubsystemBase : public SubsystemBase
{
  friend class System;  // Allow system to call our GetProxy() method.

  public:
    ProxyableSubsystemBase()
      : mProxy(new SubsystemProxy(this)) // create our proxy object
    {
    }
    ~ProxyableSubsystemBase()
    {
      mProxy->Nullify(); // inform our proxy object we are going away
    }

  protected:
    boost::shared_ptr<SubsystemProxy> GetProxy() { return mProxy; }

  private:
    boost::shared_ptr<SubsystemProxy> mProxy;
};


// the managing system
class System
{
  public:
    typedef boost::shared_ptr< SubsystemProxy > SubsystemHandle;
    typedef boost::shared_ptr< ProxyableSubsystemBase > SubsystemPtr;

    SubsystemHandle GetSubsystem( unsigned int index )
    {
        assert( index < mSubsystems.size() );
        return mSubsystems[ index ]->GetProxy();
    }

    void LogMessage( const std::string& message )
    {
        std::cout << "  <System>: " << message << std::endl;
    }

    int AddSubsystem( ProxyableSubsystemBase *pSubsystem )
    {
      LogMessage("Adding Subsystem:");
      mSubsystems.push_back(SubsystemPtr(pSubsystem));
      return mSubsystems.size()-1;
    }

    System()
    {
      LogMessage("System is constructing.");
    }

    ~System()
    {
      LogMessage("System is going out of scope.");
    }

  private:
    // have to hold base pointers
    typedef std::vector< boost::shared_ptr<ProxyableSubsystemBase> > SubsystemList;
    SubsystemList mSubsystems;
};

// the actual Subsystem
class Subsystem : public ProxyableSubsystemBase
{
  public:
    Subsystem( System* pParentSystem, const std::string ID )
      : mParentSystem( pParentSystem )
      , mID(ID)
    {
         mParentSystem->LogMessage( "Creating... "+mID );
    }

    ~Subsystem()
    {
         mParentSystem->LogMessage( "Destroying... "+mID );
    }

    // implements pure virtual functions from SubsystemBase
    void DoSomething(void) { mParentSystem->LogMessage( mID + " is DoingSomething (tm)."); }
    int GetSize(void) { return sizeof(Subsystem); }

  private:
    System * mParentSystem; // raw pointer to avoid cycles - can also use weak_ptrs
    std::string mID;
};



//////////////////////////////////////////////////////////////////
// Actual Use Example
int main(int argc, char* argv[])
{

  std::cout << "main(): Creating Handles H1 and H2 for Subsystems. " << std::endl;
  System::SubsystemHandle H1;
  System::SubsystemHandle H2;

  std::cout << "-------------------------------------------" << std::endl;
  {
    std::cout << "  main(): Begin scope for System." << std::endl;
    System mySystem;
    int FrankIndex = mySystem.AddSubsystem(new Subsystem(&mySystem, "Frank"));
    int ErnestIndex = mySystem.AddSubsystem(new Subsystem(&mySystem, "Ernest"));

    std::cout << "  main(): Assigning Subsystems to H1 and H2." << std::endl;
    H1=mySystem.GetSubsystem(FrankIndex);
    H2=mySystem.GetSubsystem(ErnestIndex);


    std::cout << "  main(): Doing something on H1 and H2." << std::endl;
    H1->DoSomething();
    H2->DoSomething();
    std::cout << "  main(): Leaving scope for System." << std::endl;
  }
  std::cout << "-------------------------------------------" << std::endl;
  std::cout << "main(): Doing something on H1 and H2. (outside System Scope.) " << std::endl;
  H1->DoSomething();
  H2->DoSomething();
  std::cout << "main(): No errors from using handles to out of scope Subsystems because of Proxy to Null Object." << std::endl;

  return 0;
}

Вывод от кода:

main(): Creating Handles H1 and H2 for Subsystems.
-------------------------------------------
  main(): Begin scope for System.
  <System>: System is constructing.
  <System>: Creating... Frank
  <System>: Adding Subsystem:
  <System>: Creating... Ernest
  <System>: Adding Subsystem:
  main(): Assigning Subsystems to H1 and H2.
  main(): Doing something on H1 and H2.
  <System>: Frank is DoingSomething (tm).
  <System>: Ernest is DoingSomething (tm).
  main(): Leaving scope for System.
  <System>: System is going out of scope.
  <System>: Destroying... Frank
  <System>: Destroying... Ernest
-------------------------------------------
main(): Doing something on H1 and H2. (outside System Scope.)
main(): No errors from using handles to out of scope Subsystems because of Proxy to Null Object.

Другие Мысли:

  • интересная статья я читал в одной из Игры, Программируя переговоры по книгам Драгоценных камней об использовании Несуществующих объектов для отладки и разработки. Они конкретно говорили об использовании Пустых Графических Моделей и Структур, таких как структура шахматной доски, чтобы заставить недостающие модели действительно выделиться. То же могло быть применено здесь путем изменения NullSubsystem для ReportingSubsystem, который зарегистрирует вызов и возможно стек вызовов каждый раз, когда к этому получают доступ. Это позволило бы Вам или клиентам Вашей библиотеки разыскивать, где они в зависимости от чего-то, что вышло из объема, но без потребности вызвать катастрофический отказ.

  • я упомянул в комментарии @Arkadiy, что круговая зависимость, которую он поднял между [1 134] и Subsystem, немного неприятна. Это может легко быть исправлено при наличии System, происходят из интерфейса, от которого Subsystem зависит, приложение Robert C Martin Принцип Инверсии Зависимости . Лучше все еще должен был бы изолировать функциональность, в которой Subsystem с нуждаются от их родителя, пишут интерфейс для этого, затем содержат на конструктора того интерфейса в [1 139] и передают его Subsystem с, которая содержала бы его через shared_ptr. Например, Вы могли бы иметь LoggerInterface, который Ваш Subsystem использование для записи в журнал тогда Вы могли получить CoutLogger или FileLogger от него и сохранить экземпляр такого в [1 146].
    Eliminating the Circular Dependency

27
ответ дан 30 November 2019 в 15:38
поделиться

Это выполнимо с надлежащим использованием weak_ptr класс. На самом деле Вы уже вполне близко к наличию хорошего решения. Вы правы, что Вы не можете ожидаться к "-думают" Ваши клиентские программисты, и при этом Вы не должны ожидать, что они будут всегда следовать "правилам" Вашего API (поскольку я уверен, что Вы уже знаете). Так, лучшими, которые можно действительно сделать, являются ремонтно-восстановительные работы.

я рекомендую иметь Ваш вызов к GetSubsystem, возвращаются weak_ptr, а не shared_ptr просто так, чтобы разработчик клиента мог протестировать законность указателя, всегда не требуя ссылки на него.

Точно так же имеют pParentSystem быть boost::weak_ptr<System> так, чтобы это могло внутренне обнаружить, существует ли его родитель System все еще через вызов к lock на pParentSystem наряду с проверкой на [1 110] (необработанный указатель не скажет Вам это).

Принятие Вы изменяете Ваш Subsystem класс, чтобы всегда проверить, существует ли его соответствие System объект, можно удостовериться что, если клиентский программист пытается использовать эти Subsystem объект за пределами намеченного объема, что ошибка закончится (что Вы управляете), а не необъяснимое исключение (что необходимо доверить клиентского программиста, чтобы поймать/правильно дескриптор).

Так, в Вашем примере с [1 114], дела не будут идти БУМ! Самый корректный способ обработать это в Subsystem dtor должен был бы иметь его, выглядите примерно так:

class Subsystem
{
...
  ~Subsystem() {
       boost::shared_ptr<System> my_system(pParentSystem.lock());

       if (NULL != my_system.get()) {  // only works if pParentSystem refers to a valid System object
         // now you are guaranteed this will work, since a reference is held to the System object
         my_system->LogMessage( "Destroying..." );
       }
       // Destroy this subsystem: deallocate memory, release resource, etc.             

       // when my_system goes out of scope, this may cause the associated System object to be destroyed as well (if it holds the last reference)
  }
...
};

я надеюсь, что это помогает!

11
ответ дан 30 November 2019 в 15:38
поделиться

Здесь Система ясно владеет подсистемами, и я не вижу никакой смысл в том, что совместно использовал владение. Я просто возвратил бы необработанный указатель. Если Подсистема переживает свою Систему, это - ошибка самостоятельно.

4
ответ дан 30 November 2019 в 15:38
поделиться

Я не вижу проблемы с наличием Системы:: GetSubsystem возвращают необработанный указатель (А НЕ интеллектуальный указатель) к Подсистеме. Так как клиент не ответственен за построение объектов, тогда нет никакого неявного контракта для клиента, чтобы быть ответственным за очистку. И так как это - внутренняя ссылка, должно быть разумно предположить, что время жизни объекта Подсистемы зависит от времени жизни Системного объекта. Необходимо тогда укрепить этот подразумеваемый контракт с документацией, заявив так же.

точка, что Вы не повторно присваиваете или совместно используете владение - итак, почему использование интеллектуальный указатель?

2
ответ дан 30 November 2019 в 15:38
поделиться

В Вашем примере было бы лучше, если бы Система содержала vector<Subsystem>, а не vector<shared_ptr<Subsystem> >. Его и более простой, и устраняет беспокойство, которое Вы имеете. GetSubsystem возвратил бы ссылку вместо этого.

1
ответ дан 30 November 2019 в 15:38
поделиться

Объекты стека будут выпущены в противоположном порядке, к которому они, где инстанцировано, поэтому если разработчик, использующий API, не пытается управлять интеллектуальным указателем, это обычно не собирается быть проблемой. Существуют только некоторые вещи, которые Вы не собираетесь быть способными предотвратить, лучшее, которое можно сделать, обеспечивают предупреждения во время выполнения, предпочтительно отлаживают только.

Ваш пример кажется очень как COM мне, у Вас есть подсчет ссылок на подсистемах, возвращаемых при помощи shared_ptr, но Вы пропускаете его на самом системном объекте.

, Если каждый из объектов подсистемы сделал addref на системном объекте на создании и выпуске на разрушении, Вы могли, по крайней мере, отобразить исключение, если бы подсчет ссылок был неправильным, когда системный объект уничтожается рано.

Использование weak_ptr также позволило бы Вам предоставлять сообщение instead/aswell как аварийное завершение, когда вещи освобождены в неправильном порядке.

1
ответ дан 30 November 2019 в 15:38
поделиться

Сущность Вашей проблемы является циклической ссылкой: Система относится к Подсистеме, и Подсистема, в свою очередь, относится к Системе. Этот вид структуры данных не может быть легко обработан подсчетом ссылок - это требует надлежащей сборки "мусора". Вы пытаетесь повредить цикл при помощи необработанного указателя для одного из краев - это только произведет больше сложностей.

По крайней мере два хороших решения были предложены, таким образом, я не попытаюсь превзойти предыдущие плакаты. Я могу только отметить, что в решении @Aaron у Вас может быть прокси для Системы, а не для Подсистем - dependingo n, что более сложно и что имеет смысл.

0
ответ дан 30 November 2019 в 15:38
поделиться

The real problem here is your design. There is no nice solution, because the model doesn't reflect good design principles. Here's a handy rule of thumb I use:

  • If an object holds a collection of other objects, and can return any arbitrary object from that collection, then remove that object from your design.

I realise that your example is contrived, but its an anti-pattern I see a lot at work. Ask yourself, what value is System adding that std::vector< shared_ptr > doesnt? Users of your API need to know the interface of SubSystem (since you return them), so writing a holder for them is only adding complexity. At least people know the interface to std::vector, forcing them to remember GetSubsystem() above at() or operator[] is just mean.

Your question is about managing object lifetimes, but once you start handing out objects, you either lose control of the lifetime by allowing others to keep them alive (shared_ptr) or risk crashes if they are used after they have gone away (raw pointers). In multi-threaded apps its even worse - who locks the objects you are handing out to different threads? Boosts shared and weak pointers are a complexity inducing trap when used in this fashion, especially since they are just thread safe enough to trip up inexperienced developers.

If you are going to create a holder, it needs to hide complexity from your users and relieve them of burdens you can manage yourself. As an example, an interface consisting of a) Send command to subsystem (e.g. a URI - /system/subsystem/command?param=value) and b) iterate subsystems and subsystem commands (via an stl-like iterator) and possibly c) register subsystem would allow you to hide almost all of the details of your implementation from your users, and enforce the lifetime/ordering/locking requirements internally.

An iteratable/enumerable API is vastly preferable to exposing objects in any case - the commands/registrations could be easily serialised for generating test cases or configuration files, and they could be displayed interactively (say, in a tree control, with dialogs composed by querying the available actions/parameters). You would also be protecting your API users from internal changes you may need to make to the subsystem classes.

I would caution you against following the advice in Aarons answer. Designing a solution to an issue this simple that requires 5 different design patterns to implement can only mean that the wrong problem is being solved. I'm also weary of anyone who quotes Mr Myers in relation to design, since by his own admission:

"I have not written production software in over 20 years, and I have never written production software in C++. Nope, not ever. Furthermore, I’ve never even tried to write production software in C++, so not only am I not a real C++ developer, I’m not even a wannabe. Counterbalancing this slightly is the fact that I did write research software in C++ during my graduate school years (1985-1993), but even that was small (a few thousand lines) single-developer to-be-thrown-away-quickly stuff. And since striking out as a consultant over a dozen years ago, my C++ programming has been limited to toy “let’s see how this works” (or, sometimes, “let’s see how many compilers this breaks”) programs, typically programs that fit in a single file".

Not to say that his books aren't worth reading, but he has no authority to speak on design or complexity.

2
ответ дан 30 November 2019 в 15:38
поделиться

Вы были в самом начале в своем первом абзаце. Ваши проекты, основанные на RAII (например, мой и наиболее хорошо написанный код C ++), требуют, чтобы ваши объекты удерживались указателями исключительного владения. В Boost это будет scoped_ptr.

Итак, почему вы не использовали scoped_ptr. Это, безусловно, произойдет потому, что вы хотели, чтобы преимущества weak_ptr защищали от висящих ссылок, но вы можете указать только weak_ptr на shared_ptr. Итак, вы приняли обычную практику целесообразного объявления shared_ptr, когда на самом деле вам нужно было единоличное владение. Это ложное объявление и, как вы говорите, компрометирует вызываемые деструкторы в правильной последовательности. Конечно, если вы никогда не поделитесь правом собственности, вам это сойдет с рук, но вам придется постоянно проверять весь свой код, чтобы убедиться, что он никогда не был передан.

Что еще хуже, boost :: weak_ptr неудобен для использовать (не имеет -> operator), поэтому программисты избегают этого неудобства, ошибочно объявляя пассивные ссылки наблюдения как shared_ptr. Это, конечно, разделяет право собственности, и если вы забудете обнулить этот shared_ptr, то ваш объект не будет уничтожен или его деструктор не будет вызван, когда вы намереваетесь это сделать.

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

Я шел тем же путем, что и вы. Защита от висящих указателей крайне необходима в C ++, но библиотека boost не обеспечивает приемлемого решения. Мне нужно было решить эту проблему - мой отдел программного обеспечения хотел получить уверенность в том, что C ++ можно сделать безопасным. Так что я накатил свой собственный - это было довольно много работы, и его можно найти по адресу:

http://www.codeproject.com/KB/cpp/XONOR.aspx

Он полностью подходит для однопоточной работы. и я собираюсь обновить его, чтобы включить указатели, совместно используемые потоками. Его ключевая особенность заключается в том, что он поддерживает интеллектуальных (самообнуляющихся) пассивных наблюдателей за объектами, находящимися в исключительном владении.

К сожалению, программисты соблазнились сборкой мусора и решениями «один размер для всех» с интеллектуальными указателями и в значительной степени даже не думают о собственности и пассивных наблюдателях - в результате они даже не знают, что делают неправильно, и не жалуются. Ересь против Boost почти неслыханная!

Предлагаемые вам решения абсурдно сложны и совершенно бесполезны. Они являются примерами абсурда, возникающего из культурного нежелания признавать, что указатели объектов имеют разные роли, которые должны быть правильно объявлены, и слепой веры в то, что Boost должен быть решением.

3
ответ дан 30 November 2019 в 15:38
поделиться
Другие вопросы по тегам:

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