Мне поручено создать класс C
, который автоматически отслеживает количество существующих экземпляров, и иметь функцию, которая возвращает это число.
Вот что у меня есть:
class C{
public:
static int num;
C(){++num;}
~C(){--num;}
int get_number_objs(){return num;}
};
int C::num = 0;
Это помогает?
Это выглядит просто и может иметь смысл, но мне интересно, есть ли крайние случаи, когда вы возитесь с указателями или чем-то в этом роде, когда что-то проваливается.
Это проверка решения больше, чем что-либо еще.
Так что мне нужно было бы перегрузить конструктор копирования и конструкторы назначения копирования, верно?
А также перемещать конструктор и перемещать оператор присваивания, где это необходимо. Хотя я считаю, что в большинстве случаев вы можете по умолчанию использовать операторы присваивания. Они не будут увеличивать или уменьшать счет.
@RedRubber только конструктор копирования (и конструктор перемещения). Оператор присваивания просто копирует/перемещает данные-члены из существующего экземпляра в другой существующий экземпляр, он не создает новый экземпляр, это делают только конструкторы.
Это помогает?
Почти. Вам также необходимо увеличить num
внутри конструктора копирования класса, а также конструктора перемещения в C++ 11 и более поздних версиях.
Кроме того, нет смысла иметь get_number_objs()
, если num
есть public
, но поскольку это подвергает num
вмешательству извне, вместо этого num
должно быть private
. А get_number_objs()
должно быть static
.
Попробуй это:
class C{
private:
static size_t num;
public:
C(){ ++num; }
C(const C&){ ++num; }
C(C&&){ ++num; }
~C(){ --num; }
static size_t get_number_objs(){ return num; }
};
size_t C::num = 0;
В качестве альтернативы, в C++ 17 и более поздних версиях вы можете inline
переменную num
, поэтому вам не нужно определять ее отдельно вне класса:
class C{
private:
static inline size_t num = 0;
public:
C(){ ++num; }
C(const C&){ ++num; }
C(C&&){ ++num; }
~C(){ --num; }
static size_t get_number_objs(){ return num; }
};
Это имеет смысл. Однако я хотел уточнить одну вещь: теперь, когда мы объявили конструктор перемещения, мы обязаны также перегрузить присваивание копии, да? (В любом случае для копирования присваивание не должно увеличивать num). Если я прав, что произойдет, если мы полностью пропустим перегрузку назначения перемещения? В какой ситуации может произойти сбой? Примеры, которые я могу придумать, когда в игру вступает конструктор перемещения, перемещенный из объектов, в любом случае являются временными.
"Мы обязаны также перегрузить назначение копирования, да?" - нет, потому что в вашем классе нет нестатических элементов данных, которые нужно копировать/перемещать, поэтому операторов по умолчанию, сгенерированных компилятором, будет достаточно. В любом случае, вам действительно нужно уяснить себе разницу между построением и заданием, это две разные вещи.
Я думал, что в С++ 11 и выше определяемый пользователем конструктор перемещения отключает неявный конструктор копирования. Я имею в виду, это не проблема определить это самостоятельно, я просто удостоверяюсь.
Показанный класс не имеет неявно сгенерированного конструктора копирования, он имеет определяемый пользователем конструктор копирования: C(const C&)
. В любом случае, вы должны больше узнать о Конструкторах копирования , Операторе присваивания копирования , Конструкторах перемещения и Операторе присваивания перемещения
Например, когда я копирую и вставляю это, я получаю note: 'constexpr C& C::operator=(const C&)' is implicitly declared as deleted because 'C' declares a move constructor or move assignment operator
. (Чтобы получить это, я только что сделал C a; C b; b = a;
). Ссылка на то, что я бегала
Спасибо за помощь, я пытаюсь разобраться во всех видах конструкторов.
Вы все еще путаете конструкцию с присваиванием. Но да, неявный оператор присваивания копии delete
'd, если присутствует пользовательский конструктор перемещения (явный пользовательский оператор не delete
'd), поэтому вам придется явно объявить пользовательский оператор присваивания копирования для b = a;
работать в таком случае. На заметку: C b = a;
на самом деле построение, а не присваивание.
Согласованный; Ранее я имел в виду присваивание копирования, а не конструкцию копирования. Что возвращает меня к моему предыдущему вопросу, если вы не возражаете - если бы я сам не определял конструктор перемещения и полагался на неявно определенное назначение копирования (которое должно работать нормально, как вы сказали), какие ситуации привели бы к сбою ? Я сталкивался только с примерами вызова конструктора перемещения, когда запрашиваются и быстро удаляются временные объекты, поэтому я не уверен, действительно ли он здесь нужен. Итак, что когда мы потерпим неудачу, если перегрузим только конструктор копирования и ничего больше?
@RedRubber Нет, если класс можно только копировать, а нельзя перемещать. Так работали классы до того, как в C++11 была добавлена семантика перемещения. Пока вы правильно следуете Правилу Трех (которое стало Правилом Пяти в C++11), все будет в порядке.
Счетчик должен быть std::atomic<std::size_t>
, чтобы избежать условий гонки и переполнения.
@Ben это полезно, только если класс используется в нескольких потоках. Но это не предотвратит переполнения. И шансов на создание миллиардов активных экземпляров практически нет (стандартные контейнеры также переполнились бы, если бы это произошло)
Под «предотвратить переполнение» я имел в виду «использование unsigned
для подсчета количества экземпляров рискованно», поскольку unsigned
обычно имеет значение uint32 и поэтому достигает ~ 4,3 байт.
@Ben Для size_t
нет гарантии размера, за исключением того, что он не может быть меньше 16 бит, поэтому он может быть эквивалентен unsigned short
или unsigned int
, хотя обычно он больше похож на unsigned long long
. Это зависит от реализации. Но вы правы, для счетчика объектов size_t
имеет больше смысла, чем unsigned
. Я обновил свои примеры.
Интересный. Я предполагал, что sizeof(std::size_t) == sizeof(void*)
и, следовательно, std::size_t
могут по определению перечислять все живые объекты любого типа.
Кажется, это согласуется со мной: en.cppreference.com/w/cpp/types/size_t если std::size_t
16 бит, то не может ли char[]
иметь длину не более 65535?
@Ben «std::size_t
может хранить максимальный размер теоретически возможного объекта любого типа (включая массив)» — это не имеет ничего общего с void*
, который может хранить адрес объекта, а не его размер.
Итак, существуют ли настоящие системы, которые имеют более 65536 байт адресного пространства, но не допускают буферов большего размера и поэтому могут иметь 16-битные size_t
?
Проверьте это с
C c; C d = c; std::cout << d.get_number_objs();
. Или посмотрите на это с другой стороны: вы явно нарушаете правило трех, так что, скорее всего, что-то не так.