Это хороший код? (оператор копирования и оператор присваивания)

По той или иной причине я вынужден предоставить как конструктор копирования, так и оператор = для моего класса. Я думал, что мне не нужно operator=, если я определил копию ctor, но QList хочет ее. Отложив это в сторону, я ненавижу дублирование кода, так что есть что-то не так с этим?

Fixture::Fixture(const Fixture& f) {
    *this = f;
}

Fixture& Fixture::operator=(const Fixture& f) {
    m_shape         = f.m_shape;
    m_friction      = f.m_friction;
    m_restitution   = f.m_restitution;
    m_density       = f.m_density;
    m_isSensor      = f.m_isSensor;
    return *this;
}

И просто из любопытства, нет способа переключить его так, чтобы основная часть кода была в копии ctor и operator= каким-то образом ее использует? Я пробовал return Fixture(f);, но мне это не понравилось.


Мне кажется, мне нужно сделать более понятным, что конструктор копирования и оператор присваивания были неявно отключены классом, который я наследую. Зачем? Потому что это абстрактный базовый класс, который не следует создавать самостоятельно. Этот класс, однако, предназначен для самостоятельной работы.

Ответы

Ответ 1

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

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

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

Fixture::Fixture():m_data(), m_size() { }

Fixture::Fixture(const Fixture& f) {
    m_data = new item[f.size()];
    m_size = f.size();
    std::copy(f.data(), f.data() + f.size(), m_data);
}

Fixture::~Fixture() { delete[] m_data; }

// note: the parameter is already the copy we would
// need to create anyway. 
Fixture& Fixture::operator=(Fixture f) {
    this->swap(f);
    return *this;
}

// efficient swap - exchanging pointers. 
void Fixture::swap(Fixture &f) {
    using std::swap;
    swap(m_data, f.m_data);
    swap(m_size, f.m_size);
}

// keep this in Fixture namespace. Code doing swap(a, b)
// on two Fixtures will end up calling it. 
void swap(Fixture &a, Fixture &b) {
  a.swap(b);
}

Как обычно я пишу оператор присваивания. Прочитайте Хотите скорость? Передайте по значению о необычной сигнатуре оператора присваивания (перейдите по значению).

Ответ 2

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

Ответ 3

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

Ответ 4

Я думаю, что у вас возникают проблемы, если ваш оператор = когда-либо становится виртуальным.

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

Ответ 5

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

В книге Джеймса Коплиена 1991 года Advanced С++, это описано как часть "Православной канонической формы". В нем он выступает за конструктор по умолчанию, конструктор копирования, оператор присваивания и деструктор.

В общем, вы должны использовать ортодоксальную каноническую форму, если:

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

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

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

Ответ 6

Я думаю, что вы должны инициализировать переменные-члены объекта, используя initializer list. Если ваши переменные имеют значение primitive-types, то это не имеет значения. В противном случае назначение отличается от инициализации.


Вы можете сделать это с помощью небольшого трюка, инициализируя указатели внутри copy constructor до 0, затем вы можете безопасно вызвать удаление в assignment operator:

Fixture::Fixture(const Fixture& f) : myptr(0) {
    *this = f;
}
Fixture& Fixture::operator=(const Fixture& f) {
    // if you have a dynamic array for example, delete other wise.
    delete[] myptr;
    myptr = new int[10];
    // initialize your array from the other object here.
    ......
    return *this;
}