Установка сетевого соединения в конструкторе: хороший или плохой?

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

Это менее ужасный к:

  1. Обработайте установление соединения в конструкторе, выдав исключение, если процесс не там.
  2. Обработайте установление соединения в отдельном connect() метод, возвращая код ошибки, если процесс не там.

В опции 1), код вызова должен будет, конечно, обернуть свое инстанцирование того класса и всего остального, что имеет дело с ним в a try() блок. В опции 2 это может просто проверить возвращаемое значение от подключения (), и возврат (уничтожающий объект), если это перестало работать, но это менее RAII-совместимо,

Связано, если я иду с опцией 1), это лучше для броска одного из станд.:: классы исключений, получите мой собственный класс исключений оттуда, прокрутите мой собственный неполученный класс исключений или просто бросьте строку? Я хотел бы включать некоторый признак отказа, который, кажется, исключает первый из них.

Отредактированный для разъяснения: удаленный процесс находится на той же машине, таким образом, довольно маловероятно что ::connect() вызов заблокируется.

10
задан ceo 27 January 2010 в 05:17
поделиться

9 ответов

Я считаю, что он считаю блокировкой Connect () в конструкторе, потому что блокирующая природа не является чем-то, как правило, ожидает от построения объекта. Таким образом, пользователи вашего класса могут быть запутаны этой функциональностью.

Что касается исключения, я думаю, что это, как правило, лучше всего (но также самая работа), чтобы получить новый класс от STD :: исключение. Это позволяет Cativer выполнять действие для этого конкретного типа исключения с помощью CALL (Const MyException & E) {...} , а также сделать одну вещь для всех исключений с помощью Cav ( Const std :: Исключение и е) {...} .

См. Этот вопрос связанный: Сколько работы должно быть сделано в конструкторе?

6
ответ дан 3 December 2019 в 23:50
поделиться

Не подключайтесь из конструктора, конструктора, который блокирует неожиданный и плохой дизайн API.

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

1
ответ дан 3 December 2019 в 23:50
поделиться

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

Тем не менее, вы правы, что клиенты класса могут понадобиться осознавать, что конструктор может заблокировать или сбой. По этой причине я мог бы сделать конструктор частным и использовать статический фабричный метод (названный конструктор IDIOM), так что клиенты должны сделать явную MakeConnection .

Это все еще обязанность клиента, чтобы определить, не имеет к нему соединение, или может ли он справиться с автономным режимом. В прежнем случае он может владеть соединением по значению и пусть любая неспособность подключения к своим клиентам; В последнем он может владеть объектом через указатель, предпочтительно «умный». В последнем случае он может попытаться построить построение собственного соединения в его конструкторе, или она может отложить его до необходимости.

E.G. (Предупреждение: код всех совершенно непроверенных)

class Connection
{
    Connection(); // Actually make the connection, may throw
    // ...

public:
    static Connection MakeConnection() { return Connection(); }

    // ...
};

Вот класс, который требует рабочего соединения.

class MustHaveConnection
{
public:
    // You can't create a MustHaveConnection if `MakeConnection` fails
    MustHaveConnection()
        : _connection(Connection::MakeConnection())
    {
    }

private:
    Connection _connection;
};

Вот класс, который может работать без одного.

class OptionalConnection
{
public:
    // You can create a OptionalConnectionif `MakeConnection` fails
    // 'offline' mode can be determined by whether _connection is NULL
    OptionalConnection()
    {
        try
        {
            _connection.reset(new Connection(Connection::MakeConnection()));
        }
        catch (const std::exception&)
        {
            // Failure *is* an option, it would be better to capture a more
            // specific exception if possible.
        }
    }

    OptionalConnection(const OptionalConnection&);
    OptionalConnection& operator=(const OptionalConnection&);

private:
    std::auto_ptr<Connection> _connection;
}

И, наконец, тот, который создает один по требованию и проторит исключения для абонента.

class OnDemandConnection
{
public:
    OnDemandConnection()
    {
    }

    OnDemandConnection(const OnDemandConnection&);
    OnDemandConnection& operator=(const OnDemandConnection&);

    // Propgates exceptions to caller
    void UseConnection()
    {
        if (_connection.get() == NULL)
            _connection.reset(new Connection(Connection::MakeConnection()));

        // do something with _connection
    }

private:
    std::auto_ptr<Connection> _connection;
}
1
ответ дан 3 December 2019 в 23:50
поделиться

По мысли РАИИ, разве это не хорошо по определению? Приобретение - это Инициализация.

0
ответ дан 3 December 2019 в 23:50
поделиться

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

Также целесообразно создать новый класс исключения, полученный из STD :: Исключение вместо того, чтобы бросать простые данные или даже бросать другие исключения STL. Вы также можете получить свой класс исключения из более конкретного описания вашей ошибки (например, вытекаете из STD :: Runtime_Error ), но этот подход является менее распространенным.

1
ответ дан 3 December 2019 в 23:50
поделиться

Что касается бросков исключений, его совершенно нормально, чтобы создать свои собственные занятия. Как гипотетический пользователь, который я бы предпочел, если они получены из STD :: Исключение или, возможно, STD :: Runtime_Error (который позволяет пройти строку ошибки к CTOR).

Пользователи, которые хотят, чтобы устроить ваш производный тип, но общая идиома:

   try {
     operation_that_might_throw ();
   } catch (std::exception& e) {
     cerr << "Caught exception: " << e.what() << endl;
   }

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

3
ответ дан 3 December 2019 в 23:50
поделиться

Я думаю, что вариант 1 - лучший подход, но вам нужно подумать, как вы ожидаете, что потребитель класса использовать это? Только тот факт, что они проводят его, достаточно хорош, чтобы идти вперед и подключиться (вариант 1) или тот факт, что у них должна быть возможность вызововать Connect (), когда они хороши и готовы (вариант 2)?

Raii также поддерживает сухое принцип (не повторять себя). Однако с вариантом 1 необходимо обеспечить обработку исключений, и вы не попадаете в гоночные условия. Как вы знаете, если в конструкторе есть исключение в конструкторе, деструктор не будет призван очистить. Также быть изменены любыми статическими функциями, которые у вас могут быть, поскольку вам понадобится блокировки вокруг тех, которые также приводят к спиральному пути.

Если вы не видели этого поста , но это хорошо прочитать.

1
ответ дан 3 December 2019 в 23:50
поделиться

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

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

0
ответ дан 3 December 2019 в 23:50
поделиться

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

Я нашёл другую причину не делать соединение в конструкторе: это означает, что я либо должен обрабатывать teardown в структурнике de, либо иметь отдельный вызов disconnect() без отдельного вызова connect(), что забавно пахнет. Деструктор является нетривиальным и может блокировать или бросать, поэтому делать это в деструкторе, вероятно, менее чем идеально.

0
ответ дан 3 December 2019 в 23:50
поделиться
Другие вопросы по тегам:

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