Компилятор C ++ ‘мелкие’ копии и назначения

Я беру класс по объектно-ориентированному программированию с использованием C ++.

В нашем тексте сказано:

Если мы не объявляем конструктор копирования, компилятор вставляет код
это реализует мелкую копию. Если мы не объявляем уступку
оператор, компилятор вставляет код, который реализует мелкий
назначение.

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

Это не вопрос о конструкторах копирования, речь идет о поведении компилятора.

РЕДАКТИРОВАТЬ> Больше контекста

Копировать конструктор как определено текстом:

Определение конструктора копирования содержит логику, которая

  1. выполняет поверхностное копирование всех переменных экземпляра, не связанных с ресурсами
  2. выделяет память для каждого нового ресурса
  3. копирует данные из исходного (ых) ресурса (ов) во вновь созданный (ые) ресурс (ы)

Ресурс как определено текстом

Память, которую объект выделяет во время выполнения, представляет ресурс этого
класс объекта.

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

6

Решение

Точнее сказать, что компилятор определяет дефолт конструктор копирования и дефолт оператор копирования копирования. Они будут копировать / создавать новый объект, просто вызывая конструктор копирования для всех переменных-членов.

  • Для примитивов, таких как intс и floatс, это обычно не проблема.
  • Для указателей, хотя. Это плохие новости! Что происходит, когда первый объект удаляет этот указатель? Теперь указатель другого объекта недействителен!
  • Если переменная-член не может быть скопирована (возможно, вы использовали std::unique_ptr чтобы устранить вышеуказанную проблему), то назначение копии по умолчанию / ctor не будет работать. Как вы можете скопировать то, что не может быть скопировано? Это приведет к ошибке компилятора.

Если вы определяете свой собственный конструктор копирования / оператор присваивания, вы можете вместо этого сделать «глубокую копию». Вы можете:

  • Создайте новый объект, а не копируйте указатель
  • Явно «мелкая копия» указателя
  • Смешайте два выше, основываясь на том, что вы действительно хотите!
  • Инициализируйте переменные-члены со стандартными / пользовательскими значениями в копируемых объектах, а не копируйте то, что было в исходном объекте.
  • Отключить копирование вообще
  • И так далее

Как видите, существует множество причин, по которым вы хотите реализовать (или явно запретить) свой собственный оператор присваивания копии, конструктор копирования, их аналоги перемещения и деструктор. На самом деле, есть известная идиома C ++, известная как Правило пяти (ранее Правило 3), который может направить ваше решение о том, когда делать это.

7

Другие решения

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

Что-то вроде этого:

int* a = new int[10];

// Shallow copying
int* b = a;   // Only copies the pointer a, not what it points to

// Deep copying
int* c = new int[10];
std::copy(a, a + 10, c);  // Copies the contents pointed to by a

Проблема с мелким копированием в отношении указателей должна быть вполне очевидной: после инициализации b в приведенном выше примере у вас есть два оба указателя указывают на одну и ту же память. Если один тогда делает delete[] a; тогда оба указателя становятся недействительными. Если два указателя находятся в разных объектах некоторого класса, то между указателями нет реальной связи, и второй объект не будет знать, удалил ли первый объект свою память.

7

Код для мелкого копирования — это простое назначение для каждого поля. Если:

class S {
T f;
};
S s1, s2;

Назначение как s1=s2; эквивалентно тому, что происходит в следующем:

class S {
T f;
public:
S &operator=(const S&s) {
this->f = s.f; // and such for every field, whatever T is
}
};
S s1, s2;
s1=s2;

Об этом говорится в 12.8-8 проекта стандарта:

Неявно объявленный конструктор копирования для класса X будет иметь
форма X :: X (const X&) если

— каждый прямой или виртуальный базовый класс B из X имеет
конструктор копирования, первый параметр которого имеет тип const B& или const
летучий Б&, а также

— для всех нестатических данных-членов X, которые
типа класса M (или его массива), каждый такой тип класса имеет копию
конструктор, первый параметр которого имеет тип const M& или const
летучий М&0,123

В противном случае, неявно объявленный конструктор копирования
будет иметь вид X :: X (X&)

12.8-28 говорит:

Неявно определенный оператор назначения копирования / перемещения для несоединения
класс X выполняет пошаговое назначение копирования / перемещения своих подобъектов. […] в том порядке, в котором они были объявлены в определении класса.

2

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

class Student sealed {
private:
std::string m_strFirstName;
std::string m_strLastName;

std::vector<unsigned short> m_vClassNumbers;
std::vector<std::string> m_vTeachers;

std::vector<unsigned short> m_vClassGrades;

public:
Student( const std::string& strFirstName, const std::string& strLastName );

std::string getFirstName() const;
std::string getLastName() const;

void setClassRoster( std::vector<unsigned short>& vClassNumbers );
std::vector<unsigned short>& getClassRoster() const;

void setClassTeachers( std::vector<std::string>& vTeachers );
std::vector<std::string>& getClassTeachers() const;

void setClassGrades( std::vector<unsigned short>& vGrades );
std::vector<unsigned short>& getGrades() const;

// Notice That These Are Both Commented Out So The Compiler Will
// Define These By Default. And These Will Make Shallow / Stack Copy
// Student( const Student& c ); // Default Defined
// Student& operator=( const Student& c ); // Default Defined
};

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

class Student sealed {
private:
std::string m_strFirstName;
std::string m_strLastName;

std::vector<unsigned short> m_vClassNumbers;
std::vector<std::string> m_vTeachers;

std::vector<unsigned short> m_vClassGrades;

public:
Student( const std::string& strFirstName, const std::string& strLastName );

std::string getFirstName() const;
std::string getLastName() const;

void setClassRoster( std::vector<unsigned short>& vClassNumbers );
std::vector<unsigned short>& getClassRoster() const;

void setClassTeachers( std::vector<std::string>& vTeachers );
std::vector<std::string>& getClassTeachers() const;

void setClassGrades( std::vector<unsigned short>& vGrades );
std::vector<unsigned short>& getGrades() const;

private:
// These Are Not Commented Out But Are Defined In The Private Section
// These Are Not Accessible So The Compiler Will No Define Them
Student( const Student& c ); // Not Implemented
Student& operator=( const Student& c ); // Not Implemented
};

Где вторая версия этого класса не будет, так как я объявил их обоих закрытыми!

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

Имейте в виду, что стандартные строки библиотеки & контейнеры реализуют свой собственный конструктор копирования & Операторы присваивания! Но та же концепция применима к поведению компилятора, если класс имеет базовые типы, такие как int, float, double и т. Д. Поэтому компилятор будет обрабатывать простой класс таким же образом в соответствии с его объявлением.

class Foo {
private:
int   m_idx;
float m_fValue;

public:
explicit Foo( float fValue );

// Foo( const Foo& c ); // Default Copy Constructor
// Foo& operator=( const Foo& c ); // Default Assignment Operator
};

Вторая версия

class Foo {
private:
int   m_idx;
float m_fValue;

public:
explicit Foo( float fValue );

private:
Foo( const Foo& c ); // Not Implemented
Foo& operator=( const Foo& c ); // Not Implemented
};

Компилятор будет обрабатывать этот класс таким же образом; он не будет определять ни один из них, поскольку они не будут реализованы из-за того, что были объявлены как частные.

0
По вопросам рекламы [email protected]