Используя ссылку как участники класса для зависимостей

Я возвращаюсь к C++ после проведения некоторого времени на управляемых памятью языках, и я - suddently, отчасти потерянный относительно того, что является лучшим способом реализовать внедрение зависимости. (Я полностью продаюсь DI, потому что я нашел, что это было самым простым способом сделать дизайн, на котором делают пробную поездку, очень легким).

Теперь, просмотр ТАК и Google получил меня множество мнений о вопросе, и я немного смущен.

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

Теперь, владением объектов также занимаются (хотя не в достаточное количество деталей к моему состоянию ;)) в печально известном руководстве по стилю Google: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Smart_Pointers

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

class Addict {
   // Something I depend on (hence, the Addict name. sorry.)
   Dependency * dependency_;
public:
   Addict(Dependency * dependency) : dependency_(dependency) {
   }
   ~Addict() {
     // Do NOT release dependency_, since it was injected and you don't own it !
   }
   void some_method() {
     dependency_->do_something();
   }
   // ... whatever ... 
};    

Если Зависимость является чистым виртуальным классом (иначе плохой Интерфейс человека), то этот код помогает ввести ложную версию Зависимости (использующий что-то как насмешка Google).

Проблема, я действительно не вижу проблем, в которые я могу войти с этим видом кода, и почему я должен хотеть использовать что-либо еще, чем необработанные указатели! Случается так, что не ясно, куда зависимость прибывает из?

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

class Addict {
   // Something I depend on (hence, the Addict name. sorry.)
   const Dependency & dependency_;
  public:
   Addict(const Dependency & dependency) : dependency_(dependency) {
   }
   ~Addict() {
     // Do NOT release dependency_, since it was injected and you don't own it !
   }
   void some_method() {
     dependency_.do_something();
   }
   // ... whatever ... 
};

Но затем я получаю другой, одинаково authoritive советы против использования ссылок как участник: http://billharlan.com/pub/papers/Managing_Cpp_Objects.html

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


Сводка ответов

(Я не знаю, хорошо ли, ТАКИМ-ОБРАЗОМ-TIQUETTE, сделать это, но я добавлю пример кода для того, что я собрал из ответов...),

От различных ответов вот то, что я, вероятно, закончу тем, что делал в своем случае:

  • зависимости от передачи как ссылка (по крайней мере, для проверки ПУСТОЙ УКАЗАТЕЛЬ не возможен),
  • в общий случай, где копирование не возможно, явно запретите его и сохраните зависимости как ссылку
  • в более редком случае, где копирование возможно, зависимости от хранилища как НЕОБРАБОТАННЫЕ указатели
  • позвольте создателю зависимостей (фабрика некоторого вида) решают между выделением стека или динамическим выделением (и в последнем случае, управлении через интеллектуальный указатель)
  • установите конвенцию разделить зависимости от собственных ресурсов

Таким образом, я закончил бы с чем-то как:

class NonCopyableAddict {
    Dependency & dep_dependency_;

    // Prevent copying
    NonCopyableAddict & operator = (const NonCopyableAddict & other) {}
    NonCopyableAddict(const NonCopyableAddict & other) {}

public:
    NonCopyableAddict(Dependency & dependency) : dep_dependency_(dep_dependency) {
    }
    ~NonCopyableAddict() {
      // No risk to try and delete the reference to dep_dependency_ ;)
    }
    //...
    void do_some_stuff() {
      dep_dependency_.some_function();
    }
};

И для copyable класса:

class CopyableAddict {
    Dependency * dep_dependency_;

public: 
    // Prevent copying
    CopyableAddict & operator = (const CopyableAddict & other) {
       // Do whatever makes sense ... or let the default operator work ? 
    }
    CopyableAddict(const CopyableAddict & other) {
       // Do whatever makes sense ...
    }


    CopyableAddict(Dependency & dependency) : dep_dependency_(&dep_dependency) {
    }
    ~CopyableAddict() {
      // You might be tempted to delete the pointer, but its name starts with dep_, 
      // so by convention you know it is not your job
    }
    //...
    void do_some_stuff() {
      dep_dependency_->some_function();
    }
};

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


Сохраненный для ссылки

Как указано Martin, следующий пример не решает проблему.

Или, принятие меня имеет конструктора копии, что-то как:

class Addict {
   Dependency dependency_;
  public:
   Addict(const Dependency & dependency) : dependency_(dependency) {
   }
   ~Addict() {
     // Do NOT release dependency_, since it was injected and you don't own it !
   }
   void some_method() {
     dependency_.do_something();
   }
   // ... whatever ... 
};

24
задан user513951 30 August 2019 в 15:41
поделиться

7 ответов

Нет никакого жесткого и быстрого правила:
. Как уже упоминалось, использование ссылок внутри объектов может вызвать проблемы с копированием (и это так), поэтому это не панацея, но для определённой ситуации это может быть полезно (именно поэтому C++ предоставляет нам возможность делать всё это разными способами). Но использование RAW-указателей на самом деле не вариант. Если вы динамически выделяете объекты, то вы всегда должны поддерживать их с помощью умных указателей, а ваш объект также должен использовать умные указатели.

Для людей, которым нужны примеры: Потоки всегда передаются и хранятся в виде ссылок (так как их нельзя скопировать).

Некоторые комментарии к вашим примерам кода:

Пример один и два

Ваш первый пример с указателями. В основном это то же самое, что и второй пример с использованием ссылок. Разница в том, что ссылка не может быть NULL. Когда вы передаете ссылку, объект уже жив и, следовательно, должен иметь срок жизни больше, чем объект, который вы уже тестируете (если он был создан на стеке), так что сохранять ссылку безопасно. Если вы динамически создаете указатели в качестве зависимостей, то я бы рассмотрел возможность использования boost::shared_pointer или std::auto_ptr в зависимости от того, является ли общая собственность на зависимость или нет.

Пример третий:

Я не вижу большой пользы в вашем третьем примере. Это связано с тем, что нельзя использовать полиморфные типы (если передать объект, производный от Dependency, то он будет нарезан во время операции копирования). Таким образом, код также может быть внутри Addict, а не в отдельном классе.

Билл Харлен: (http://billharlan.com/pub/papers/Managing%5FCpp%5FObjects.html)

Чтобы ничего не отнимать у Билла Но:

  1. Я никогда о нем не слышал.
    • Он гео-физик, а не программист
    • Он переписывает программирование на Java, чтобы улучшить ваш C++
    • Языки теперь настолько разные в использовании, что это совершенно ложно)
    • Если вы хотите использовать ссылки на What to-do/not to-do.
      Тогда я бы выбрал одно из Больших имен в поле C++:
      . Stroustrup/Sutter/Alexandrescu/Meyers

Summary:

  1. Не используйте RAW указатели (когда требуется владение)
  2. Не используйте умные указатели.
  3. Не копируйте объекты в ваш объект (он будет нарезать кусочки).
  4. Вы можете использовать ссылки (но знаете ограничения).

Мой пример введения зависимостей с использованием ссылок:

class Lexer
{
    public: Lexer(std::istream& input,std::ostream& errors);
    ... STUFF
    private:
       std::istream&  m_input;
       std::ostream&  m_errors;
};
class Parser
{
    public: Parser(Lexer& lexer);
    ..... STUFF
    private:
        Lexer&        m_lexer;
};

int main()
{
     CLexer  lexer(std::cin,std::cout);  // CLexer derived from Lexer
     CParser parser(lexer);              // CParser derived from Parser

     parser.parse();
}

// In test.cpp
int main()
{
     std::stringstream  testData("XXXXXX");
     std::stringstream  output;
     XLexer  lexer(testData,output);
     XParser parser(lexer);

     parser.parse();
}
8
ответ дан 29 November 2019 в 00:25
поделиться

Реферат: Если вам нужно сохранить ссылку, сохраните указатель как приватную переменную и получите к нему доступ методом разыменования. Можно проверить, что указатель не является нулевым в инварианте объекта.

В глубине:

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

Во-вторых, тип указателя/ссылки, передаваемый в функции и конструкторы, должен указывать, кто несет ответственность за освобождение объекта и как он должен быть освобожден:

  • std::auto_ptr - вызываемая функция отвечает за освобождение, и сделает это автоматически, когда это будет сделано. Если вам нужна семантика копирования, то в интерфейсе должен быть предусмотрен метод клонирования, который должен возвращать auto_ptr.

  • std::shared_ptr - вызываемая функция отвечает за освобождение, и сделает это автоматически, когда это будет сделано и когда все остальные ссылки на объект исчезнут. Если вам нужна семантика поверхностного копирования, то сгенерированные компилятором функции будут в порядке, если нужно глубокое копирование, то интерфейс должен предоставлять метод клона, который должен возвращать shared_ptr.

  • Ссылка - вызывающая сторона несет ответственность. Вам все равно - объект может быть выделен в стек на все, что вы знаете. В этом случае нужно передавать по ссылке, но хранить по указателю. Если вам нужна семантика неглубокого копирования, то сгенерированные компилятором функции будут в порядке, если вам нужно глубокое копирование, то вы попали в беду.

  • Сырой указатель. Кто знает? Может быть выделен где угодно. Может быть нулевым. Вы можете быть ответственны за его освобождение, а можете и нет.

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

Обратите внимание, что методы, которые дают вам ответственность за освобождение объекта, не нарушают DI - освобождение объекта - это просто часть договора, который у вас есть с интерфейсом (так как вам не нужно ничего знать о конкретном типе, чтобы освободить его).

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

Но тогда я получаю другие, не менее авторские советы против использования ссылок в качестве члена : http://billharlan.com/pub/papers/Managing%5FCpp%5FObjects.html

В этом случае я думаю, что вы хотите установить объект только один раз, в конструкторе, и никогда не меняйте его, так что никаких проблем. Но если вы захотите изменить его позже, используйте функцию init, имея конструктор копирования, короче говоря, всё, что нужно будет изменить ссылку, вам придётся использовать указатели.

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

[обновление 1]
Если вы всегда можете гарантировать, что зависимость переживает зависимость, вы можете использовать необработанный указатель/ссылку, конечно. между этими двумя, я бы принял очень простое решение: указатель, если NULL разрешен, ссылка в противном случае.

(Смысл моей первоначальной заметки был в том, что ни указатель, ни ссылка не решают проблему жизни)


Я бы последовал печально известному руководству в стиле google здесь, и использовал бы умные указатели.

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

С помощью (ссылочного) умного указателя, политика становится зависимой и уничтожается, когда ее больше никто не использует . Звучит идеально.

Еще лучше: с boost::shared_ptr (или подобным интеллектуальным указателем, позволяющим политику нейтрализации типов) политика прикрепляется к объекту при построении - что обычно означает, что все, что влияет на зависимость, заканчивается в одном месте.

Типичные проблемы интеллектуальных указателей - накладные и круговые ссылки - здесь редко вступают в игру. Зависимости, как правило, не маленькие и многочисленные, а зависимость, имеющая сильную обратную ссылку на своих зависимых - это, по крайней мере, запах кода. (Тем не менее, вы должны иметь это в виду. Добро пожаловать обратно на C++)

Предупреждение: Я не "полностью продан" DI, но я полностью продан на умных указателях ;)

.

[обновление 2]
Обратите внимание, что вы всегда можете создать shared_ptr для стека/глобального экземпляра, используя нулевое удаление.
. Однако это требует поддержки с обеих сторон: наркоман должен дать гарантии, что он не передаст ссылку на зависимость кому-то другому, кто может прожить дольше, а звонящий вернется с ответственностью за обеспечение пожизненного существования. Меня не устраивает это решение, но я иногда им пользуюсь.

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

Я бы держался подальше от ссылок в качестве членов, так как они, как правило, не вызывают головной боли, если вы в конечном итоге запихиваете один из ваших объектов в контейнер STL. Я бы рассмотрел возможность использования комбинации boost::shared_ptr для владения и boost::weak_ptr для иждивенцев.

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

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

Правка: Найдено - это вопрос с различными мнениями в качестве ответов: Должен ли я предпочитать указатели или ссылки в данных участников?

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

Я могу здесь уже свою пониженную моду, но я скажу, что в классе не должно быть опорных членов ЗА ЛЮБОЙ РЕЗУЛЬТАТ, НИКОГДА. За исключением случаев, когда они являются простым постоянным значением. Причин для этого много, как только вы начинаете это, вы открываете все плохие вещи в C++. Посмотрите мой блог, если вам действительно не все равно.

0
ответ дан 29 November 2019 в 00:25
поделиться
Другие вопросы по тегам:

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