Направляющие: Виртуальные атрибуты и значения формы

Введение

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

Давайте рассмотрим простой пример:

class person
{
    std::string name;
    int age;

public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
};

int main()
{
    person a("Bjarne Stroustrup", 60);
    person b(a);   // What happens here?
    b = a;         // And here?
}

(Если вы озадачены частью name(name), age(age), это называется список инициализатора члена .)

Специальные функции-члены

Что означает копирование объекта person? Функция main показывает два разных сценария копирования. Инициализация person b(a); выполняется конструктором копирования . Его работа заключается в создании нового объекта на основе состояния существующего объекта. Назначение b = a выполняется оператором копирования . Его работа обычно немного сложнее, потому что целевой объект уже находится в каком-то допустимом состоянии, с которым нужно иметь дело.

Поскольку мы сами не объявили ни конструктор копирования, ни оператор присваивания (ни деструктор), они неявно определены для нас. Цитата из стандарта:

Конструктор [...] копирования и оператор присваивания копии, [...] и деструктор являются специальными функциями-членами. [ Примечание : Реализация будет неявно объявлять эти функции-члены для некоторых типов классов, когда программа явно не объявляет их. Реализация будет неявно определять их, если они используются. [...] конец примечания ] [n3126.pdf раздел 12 §1]

По умолчанию копирование объекта означает копирование его членов:

Неявно определенный конструктор копирования для класса X, не являющегося объединением, выполняет пошаговую копию своих подобъектов. [n3126.pdf раздел 12.8 §16]

Неявно определенный оператор присваивания копии для класса X, не являющегося объединением, выполняет присваивание для каждого элемента подобласти. [n3126.pdf раздел 12.8 §30]

Неявные определения

Неявно определенные специальные функции-члены для person выглядят следующим образом:

// 1. copy constructor
person(const person& that) : name(that.name), age(that.age)
{
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    name = that.name;
    age = that.age;
    return *this;
}

// 3. destructor
~person()
{
}

Копирование по элементам - это именно то, что нам нужно в этом случае: name и age копируются, поэтому мы получаем автономный, независимый объект person. Неявно определенный деструктор всегда пуст. Это также хорошо в этом случае, так как мы не получили никаких ресурсов в конструкторе. Деструкторы членов неявно вызываются после завершения деструктора person:

После выполнения тела деструктора и уничтожения любых автоматических объектов, размещенных в теле, деструктор для класса X вызывает деструкторы для прямых [...] членов X [n3126.pdf 12.4 §6]

Управление ресурсами

Итак, когда мы должны объявить эти специальные функции-члены явно? Когда наш класс управляет ресурсом , то есть когда объект класса отвечает за за этот ресурс. Это обычно означает, что ресурс получен в конструкторе (или передан в конструктор) и выпущен в деструкторе.

Давайте вернемся назад к предварительному стандарту C ++. Не было такого понятия, как std::string, и программисты были влюблены в указатели. Класс person мог бы выглядеть так:

class person
{
    char* name;
    int age;

public:

    // the constructor acquires a resource:
    // in this case, dynamic memory obtained via new[]
    person(const char* the_name, int the_age)
    {
        name = new char[strlen(the_name) + 1];
        strcpy(name, the_name);
        age = the_age;
    }

    // the destructor must release this resource via delete[]
    ~person()
    {
        delete[] name;
    }
};

Даже сегодня люди все еще пишут классы в этом стиле и попадают в неприятности: « Я толкнул человека в вектор, и теперь я получаю сумасшедшие ошибки памяти! "Помните, что по умолчанию копирование объекта означает копирование его членов, но копирование элемента name просто копирует указатель, а не массив символов, на который он указывает! Это имеет несколько неприятных эффектов:

  1. Изменения через a можно наблюдать через b.
  2. Как только b уничтожено, a.name является висящим указателем.
  3. Если a уничтожено, удаление висящего указателя приводит к неопределенному поведению .
  4. Поскольку в присвоении не учитывается то, на что указывалось name до присвоения, Рано или поздно у вас появятся утечки памяти повсюду.

Явные определения

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

// 1. copy constructor
person(const person& that)
{
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    if (this != &that)
    {
        delete[] name;
        // This is a dangerous point in the flow of execution!
        // We have temporarily invalidated the class invariants,
        // and the next statement might throw an exception,
        // leaving the object in an invalid state :(
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }
    return *this;
}

Обратите внимание на разницу между инициализацией и назначением: мы должны разрушить старое состояние перед назначением name, чтобы предотвратить утечки памяти. Также мы должны защищаться от самостоятельного присвоения формы x = x. Без этой проверки delete[] name удалит массив, содержащий строку source , потому что когда вы пишете x = x, и this->name, и that.name содержат один и тот же указатель.

Безопасность исключений

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

// 2. copy assignment operator
person& operator=(const person& that)
{
    char* local_name = new char[strlen(that.name) + 1];
    // If the above statement throws,
    // the object is still in the same state as before.
    // None of the following statements will throw an exception :)
    strcpy(local_name, that.name);
    delete[] name;
    name = local_name;
    age = that.age;
    return *this;
}

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

Некопируемые ресурсы

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

private:

    person(const person& that);
    person& operator=(const person& that);

В качестве альтернативы, вы можете наследовать от boost::noncopyable или объявить их как удаленные (в C ++ 11 и выше):

person(const person& that) = delete;
person& operator=(const person& that) = delete;

Правило трех

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

Если вам нужно явно объявить либо деструктору, конструктору копирования или оператору присваивания копии, вам, вероятно, нужно явно объявить все три из них.

(К сожалению, это «правило» не применяется стандартом C ++ или любым известным мне компилятором.)

Правило пяти

Из C ++ 11 на, объект имеет 2 дополнительные специальные функции-члены: конструктор перемещения и назначение перемещения. Правило пяти государств для реализации этих функций также.

Пример с подписями:

class person
{
    std::string name;
    int age;

public:
    person(const std::string& name, int age);        // Ctor
    person(const person &) = default;                // Copy Ctor
    person(person &&) noexcept = default;            // Move Ctor
    person& operator=(const person &) = default;     // Copy Assignment
    person& operator=(person &&) noexcept = default; // Move Assignment
    ~person() noexcept = default;                    // Dtor
};

Правило нуля

Правило 3/5 также упоминается как правило 0/3/5. Нулевая часть правила гласит, что вы не можете писать какие-либо специальные функции-члены при создании вашего класса.

Совет

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

5
задан Castro 21 June 2009 в 02:24
поделиться

3 ответа

Я полагаю, это потому, что ваш метод Book # editorial_string всегда будет возвращать "". Можно упростить до следующего:

  def editorial_string
   editorial ? editorial.name : ""
  end

Обновление на основе комментария:

Похоже, вы хотите создавать вложенные формы. (См. accept_nested_attributes_for в документации api ). Обратите внимание, что это новинка в Rails 2.3.

Итак, если вы обновите свой класс Book

class Book < ActiveRecord::Base
  accepts_nested_attributes_for  :editorial
  ...
end

(теперь вы также можете удалить методы editorial_string =, editorial_string)

И обновите свои формы примерно так:

...
<% f.fields_for :editorial do |editorial_form| %>
  <%= editorial_form.label :name, 'Editorial:' %>
  <%= editorial_form.text_field :name %>
<% end %>
...
4
ответ дан 14 December 2019 в 19:23
поделиться

Первая проблема заключается в том, что

def editorial_string
  self.editorial.name unless editorial.nil?
  ""
end

всегда будет возвращать "", потому что это последняя строка.

def editorial_string
  return self.editorial.name if editorial
  ""
end

исправит эту проблему. Что касается того, почему проверки не проходят, я не знаю, что вы делаете в контроллере? Какие ошибки проверки вы получаете?

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

Взгляните на этот подкаст http://railscasts.com/episodes/167-more-on-virtual-attributes . Я думаю, вам следует переместить свой find_or_create из editorial_string = (input) метод обратного вызова после сохранения.

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

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