Ваш A
класс не следует Rule of Three:
правило трех (также известное как Закон Большой тройки или Большая тройка) является эмпирическим правилом на C++ (до C++ 11), утверждающим, что если класс определяет один (или более) следующий, следует, вероятно, явно определить все три:
- деструктор
- конструктор копирования
- оператор копирующего присваивания
Эти три функции являются специальные функции-члены. Если одна из этих функций используется без предварительного объявления программистом, она будет неявно реализована компилятором со стандартной семантикой выполнения указанной операции для всех членов класса.
- Destructor - Вызов деструкторов всех членов класса типа объекта
- Copy конструктор - Построить все элементы объекта из соответствующих членов аргумента конструктора копии, назвав копировальные конструкторы Объект класса класса и выполнение простого назначения всех членов неклассического типа (например, int или указателя)
- Оператор присваивания копиям - Назначить все члены объекта из соответствующих членов операции присваивания аргумента ator, вызывая операторы присваивания копий членам класса класса объекта и выполняя простое назначение всего неклассического типа (например, int или указатель).
Правило трех утверждает, что если один из них должен был быть определен программистом, то это означает, что сгенерированный компилятором версия не соответствует требованиям класса в одном случае, и это, вероятно, не подходит в других случаях. Термин «правило трех» был придуман Маршаллом Клайн в 1991 году.
Поправки к этому правилу заключаются в том, что если класс сконструирован таким образом, что Инициализация ресурсов используется (RAII) используется для всех его (нетривиальные) члены, деструктор может остаться неопределенным (также известный как Закон Большой второй). Готовым примером такого подхода является использование интеллектуальных указателей вместо простых.
Поскольку неявно сгенерированные конструкторы и операторы присваивания просто копируют все члены данных класса («мелкая копия»), следует определить явные конструкторы копирования и операторы присваивания копий для классов, которые инкапсулируют сложные структуры данных или имеют внешние ссылки, такие как указатели , если вам нужно скопировать объекты, на которые указывают члены класса. Если поведение по умолчанию («мелкая копия») на самом деле является предполагаемым, то явное определение, хотя и избыточное, будет «самодокументирующимся кодом», указывающим на то, что это было намерение, а не надзор.
Вам нужно добавить конструктор копирования и оператор присваивания копии (и ваш деструктор должен использовать delete[]
вместо delete
):
class A
{
private:
int *array;
int size;
public:
A(int s)
: size(s), array(new int[s])
{
fprintf(stderr, "Allocated %p\n", array);
}
A(const A &src)
: size(src.size), array(new int[src.size])
{
std::copy(src.array, src.array + src.size, array);
fprintf(stderr, "Allocated %p, Copied from %p\n", array, src.array);
}
~A()
{
fprintf(stderr, "Deleting %p\n", array);
delete[] array;
}
A& operator=(const A &rhs)
{
A tmp(rhs);
std::swap(array, tmp.array);
std::swap(size, tmp.size);
return *this;
}
};
Поскольку вы упоминаете emplace_back()
, это означает, что вы используете C + +11 или более поздняя версия, а это значит, вы должны также иметь дело с ходом семантикой Rule of Five:
с появлением C++ 11 правило трех может быть расширено с правилом фи поскольку C++ 11 реализует семантику перемещения, позволяя целевым объектам захватывать (или красть) данные из временных объектов. В следующем примере также показаны новые движущиеся элементы: move constructor и move assign operator. Следовательно, для правила пяти мы имеем следующие специальные члены:
- деструкторов
- конструктор копирования
- шаг конструктора
- оператор копирующего присваивания
- Оператор присваивания шаг
Ситуации существуют, где классы могут нуждаться в деструкторах, но не могут разумно реализовать объекты копирования и перемещения, а также скопировать и переместить назначение . Это происходит, например, когда базовый класс не поддерживает эти последние члены Big Four, но конструктор производного класса выделяет память для собственного использования. [Править] В C++ 11 это можно упростить, явно указав пять как по умолчанию.
Вы должны добавить конструктор перемещения и оператора присваивания шаг в коде выше:
class A
{
private:
int *array;
int size;
public:
A(int s)
: size(s), array(new int[s])
{
fprintf(stderr, "Allocated %p\n", array);
}
A(const A &src)
: size(src.size), array(new int[src.size])
{
std::copy(src.array, src.array + src.size, array);
fprintf(stderr, "Allocated %p, Copied from %p\n", array, src.array);
}
A(A &&src)
: size(0), array(nullptr)
{
std::swap(array, src.array);
std::swap(size, src.size);
fprintf(stderr, "Moved %p, Replaced with %p\n", array, src.array);
}
~A()
{
fprintf(stderr, "Deleting %p\n", array);
delete[] array;
}
A& operator=(const A &rhs)
{
A tmp(rhs);
std::swap(array, tmp.array);
std::swap(size, tmp.size);
return *this;
}
A& operator=(A &&rhs)
{
std::swap(array, rhs.array);
std::swap(size, rhs.size);
return *this;
}
};
В противном случае, вы должны стремиться к Rule of Zero вместо:
Там это предложение R Martinho Fernandes, чтобы упростить все это в Правиле 0 для C++ (прежде всего для C++ 11 &). Правило 0 утверждает, что если вы укажете какой-либо из членов по умолчанию, то ваш класс должен иметь дело исключительно с одним ресурсом. Кроме того, он должен определить все члены по умолчанию для обработки этого ресурса (или удалить член по умолчанию, если это необходимо). Таким образом, такие классы должны следовать правилу 5, описанному выше. Ресурсом может быть любое: выделенная память, файловый дескриптор, транзакция базы данных и т. Д.
Любой другой класс не должен выделять ресурсы напрямую. Кроме того, они должны опустить члены по умолчанию (или явно назначить все из них по умолчанию через = default
).Любые ресурсы должны использоваться косвенно, используя классы с одним ресурсом в качестве переменных-членов/локальных переменных. Это позволяет таким классам наследовать элементы по умолчанию из объединения переменных-членов, тем самым автоматически пересылать перемещаемость/копируемость объединения всех базовых ресурсов. Поскольку собственность на 1 ресурс принадлежит только 1 переменной-члену, исключения в конструкторе не могут утечка ресурсов из-за RAII. Полностью инициализированные переменные будут иметь свои деструкторы, называемые &. Неинициализированные переменные не могли иметь никаких ресурсов для начала.
Поскольку большинство классов не имеют права собственности в качестве своей единственной заботы, большинство классов могут опустить члены по умолчанию. Это то, где правило-0 получает свое имя.
Исключите ваш ручной массив в целом и использовать std::vector
вместо:
class A
{
private:
std::vector<int> array;
public:
A(int s)
: array(s)
{
}
};
Нет необходимости явно определить копирования/перемещения конструктора, копировать/оператор присваивания перемещения или деструктор, потому что реализация по умолчанию при условии компилятор автоматически вызовет соответствующие функции vector
для вас.
«Вектор» копирует/перемещает существующие элементы, когда ему необходимо распределить выделенное хранилище, а ваш класс не следует [Правило три] (http://stackoverflow.com/q/4172722/241631). – Praetorian
Зачем использовать динамический массив? Используйте вектор в своем классе A. – DeathTails
Ничего себе, все это довольно странные предположения. Также этот код одинаково странный. –