Инъекция зависимостей c ++: время жизни объекта? - PullRequest
9 голосов
/ 02 ноября 2011

Я пришел из C # и пытаюсь перевести некоторые из моих практик на C ++.Я использовал внедрение зависимостей в разных местах моего кода с использованием необработанных указателей.Затем я решаю заменить необработанные указатели на std :: shared_ptr.В рамках этого процесса было предложено рассмотреть возможность использования автоматически назначаемых стековых переменных вместо их динамического распределения (см. этот вопрос , хотя этот вопрос был в контексте unique_ptr, так что, возможно, это не так).

Я полагаю, что в приведенном ниже примере показано использование автоматических переменных.

class MyClass
{ 
public:
   MyClass(ApplicationService& app): appService_(app)
   {
   }

   ~MyClass()
   {
        appService_.Destroy(something);

   }
private:   
   ApplicationService& appService_;
}

class ConsumerClass
{
    DoSomething()
    {
        CustomApplicationService customAppService;
        MyClass myclass(customAppService);
        myclass...
    }
}

В приведенном выше примере, когда customAppservice и myclass выходят из области видимости, как узнать, какой из них будет уничтожен первым?Если customAppService уничтожается первым, то деструктор MyClass завершится ошибкой.Является ли это хорошей причиной для использования shared_ptr вместо этого в этом сценарии, или есть чистый способ обойти это?

ОБНОВЛЕНИЕ

ApplicationService - это класс, являющийся оболочкой для глобальныхфункции, необходимые для взаимодействия со сторонней библиотекой, которую использует мой код.У меня есть этот класс, так как я считаю, что это стандартный способ поддержки модульного тестирования и создания заглушек / насмешек над автономными функциями.Этот класс просто делегирует вызовы соответствующим глобальным функциям.Вызов appService_.Destroy (что-то);фактически уничтожает объект, используемый каждым конкретным экземпляром MyClass, не уничтожая ничего, что связано с самим классом Application.

Ответы [ 2 ]

7 голосов
/ 02 ноября 2011

Ответ таков: вам все равно не нужно знать, поскольку ваш дизайн нарушен.

Во-первых, Destroy звучит как плохая идея, более того, если он вызывается в объекте, который не являетсяответственность за уничтожение другого объекта.Код из метода Destroy принадлежит деструктору ApplicationService (который, будем надеяться, виртуален, хотя в этом случае он фактически не нужен), который в отличие от C # вызывается в совершенно определенный момент времени.

Как только вы это сделаете, вы (надеюсь) поймете, что MyClass не несет ответственности за уничтожение appService_, поскольку оно не владеет им.За это отвечает ConsumerClass (точнее, метод DoSomething), который действительно управляет реальной службой и фактически уничтожает ее автоматически, как только вы переместили код Destroy в деструктор.Разве не приятно, что RAII делает все чисто и автоматически?

class MyClass
{ 
public:
   MyClass(ApplicationService& app): appService_(app)
   {
   }

private:   
   ApplicationService& appService_;
}

class ConsumerClass
{
    DoSomething()
    {
        CustomApplicationService customAppService;
        MyClass myclass(customAppService);
        myclass...
    }
}

class ApplicationService
{
public:
    virtual ~ApplicationService()
    {
        //code from former Destroy method
    }
}

class CustomApplicationService
{
public:
    virtual ~CustomApplicationService()
    {
        //code from former Destroy method
    }
}

ИМХО это идеальный чистый C ++ способ обойти это, и проблема определенно не является причиной для спама shared_ptrs.Даже если вам действительно нужен выделенный метод Destroy и вы не можете переместить код в деструктор (который я бы выбрал в качестве мотивации для переосмысления проекта), вы все равно будете вызывать Destroy из DoSomething, как снова, MyClass не несет ответственности за уничтожение appService_ .

EDIT: Согласно вашему обновлению (и моему глупому игнорированию аргумента something), ваш дизайн действительно выглядит вполне корректным(по крайней мере, если вы не можете возиться с изменением ApplicationService), извините.

Несмотря на то, что члены класса должны быть уничтожены в обратном порядке построения, я не уверен, что это также относится к локальным автоматическим переменным.Чтобы удостовериться, что деструкторы вызываются в определенном порядке, вы можете ввести вложенные области видимости, используя простые блоки:

void DoSomething()
{
    CustomApplicationService customAppService;
    {
        MyClass myclass(customAppService);
        myclass...
    }       // myclass destroyed
}       // customAppService destroyed

Конечно, по-прежнему совершенно нет необходимости использовать динамическое распределение, не говоря уже о shared_ptrs.Хотя вложенные блоки немного разрушают код, это ничто не против уродства динамического выделения, примененного не динамическим образом и без причины, и, по крайней мере, «хорошо выглядит семантическим способом» с объявлением customAppService сверхублока;)

2 голосов
/ 02 ноября 2011

В C ++ объекты, как правило, уничтожаются в порядке, прямо противоположном порядку, в котором они были созданы.

Согласно вашему примеру, MyClass будет уничтожено до CustomApplicationService

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

Другая тонкость называется статический порядок инициализации фиаско .Однако это не относится к автоматическим (стековым) переменным.

Редактировать:
Начиная с C ++ 2003 - искал «обратный порядок»

6.6.0.2

On exit from a scope (however accomplished), destructors (12.4) are called for all 
constructed objects with automatic storage duration (3.7.2) (named objects or 
temporaries) that are declared in that scope, in the reverse order of their
declaration. ... [Note: However, the program can be terminated (by calling exit()
or abort()(18.3), for example) without destroying class objects with automatic
storage duration. ]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...