Подтвердить что ты не робот

Должен ли объект С++ всегда находиться в допустимом состоянии?

Всякий раз, когда объект сконструирован, должен ли конструктор всегда оставлять его в "инициализированном" состоянии?

Например, если класс Image имеет два конструктора, в которых один принимает строку пути к файлу, а другой не принимает никаких параметров, является ли плохой практикой для последнего оставить объект изображения в недопустимом состоянии?

Предполагая, что класс написан для обработки обоих состояний.

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

EDIT: я знаю список инициаторов участников. Я нашел несколько ситуаций, когда я хотел бы построить объект ВО ВРЕМЯ конструктора класса, в котором он хранится, а не раньше. Хотя, я понимаю, что это потенциально может быть более опасным, чем любая другая альтернатива.

4b9b3361

Ответ 1

Все это сводится к определению "правильного состояния": если методы вашего класса обрабатывают состояние, когда путь пуст, то состояние с пустым путём является допустимым состоянием и, безусловно, приемлемо.

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

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

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

Ответ 2

Ваша последняя строка:

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

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

class Member {
public:
  Member(std::string str) { std::cout << str << std::endl; }
};

class Foo {
public:
  Foo() : member_("Foo") {}
private:
  Member member_;
}

Кроме того, ваше название вопроса и конфликт с телом и терминология немного расплывчаты. При построении обычно лучше оставить объект в допустимом и пригодном для использования состоянии. Иногда второй аспект (используемый) менее необходим, и многие решения требуют его. Кроме того, в С++ 11 переход от объекта должен оставить его в допустимом состоянии, но необязательно (и во многих случаях не должен) оставить его в пригодном для использования состоянии.

РЕДАКТИРОВАТЬ: Чтобы решить вашу проблему с выполнением работы в вашем конструкторе, подумайте о переносе работы либо статическому члену класса-члена, либо частной (статической или нестатической) функции в владеющий классом:

class Member {
public:
  Member(std::string str) { std::cout << str << std::endl; }
};

class Foo {
public:
  Foo() : member_(CreateFoo()) {}
private:
  Member CreateMember() {
    std::string str;
    std::cin >> str;
    return Member(str);
  }
  Member member_;
};

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

Ответ 3

Да, он всегда должен быть действительным. Однако, как правило, он не очень четко определен, что делает объект действительным. По крайней мере, объект должен быть использован в любом случае без сбоев. Это, однако, не означает, что все операции могут выполняться на объекте, но должно быть как минимум одно. Во многих случаях это просто assignment из другого источника (например, итераторы контейнеров std) и destruction (этот обязателен, даже после перемещения). Но там больше операций, поддерживаемых объектами в любом состоянии, тем меньше подвержено ошибкам.

Обычно это компромисс. Если вы можете уйти с объектами, имеющими только состояния, в которых все операции действительны, это, безусловно, здорово. Однако эти случаи встречаются редко, и если вам нужно перепрыгнуть через обручи, чтобы добраться туда, обычно проще просто добавить и документировать предварительные условия для некоторых своих функций. В некоторых случаях вы даже можете разделить интерфейс, чтобы различать функции, которые делают этот компромисс, и те, которые этого не делают. Популярным примером этого является std::vector, где вам нужно иметь достаточное количество элементов в качестве предварительного условия использования operator[]. С другой стороны, функция at() будет работать, но выдает исключение.

Ответ 4

Сначала давайте определим, что именно "действительное состояние": это состояние, в котором объект может выполнять свою работу.
Например, если мы выписываем класс, который управляет файлом и позволяем нам писать и читать файл, допустимым состоянием (по нашему определению) может быть состояние, в котором объект держит корректно обработанный файл и его готов к чтению/напишите на нем.

Но рассмотрим другую ситуацию: Каково состояние перемещенного значения?

File::File( File&& other )
{
    _file_handle = other._file_handle;
    other._file_handle = nullptr; //Whats this state?
} 

Это состояние, в котором объект файла не готов к записи/чтению в файле, , но готов к инициализации. То есть, готов к инициализации состояния.

Теперь рассмотрим альтернативную реализацию вышеуказанного ctor с помощью идиомы копирования и подкачки:

File::File() :
    _file_handle{ nullptr }
{} 

File::File( File&& other ) : File() //Set the object to a ready to initialice state
{
    using std::swap; //Enable ADL

    swap( *this , other );
}

Здесь мы используем по умолчанию ctor помещаем объект в состояние готовности к инициализации и просто меняем пройденное rvalue с этим объектом, в результате чего происходит то же поведение, что и первая реализация.

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

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

Ответ 5

Обычно да. Я видел несколько хороших контрпримеров, но они настолько редки.