-1

I am tasked to have a class C which automatically keeps track of the number of its instances that exists, and to have a function that returns this number.

Here is what I have:

class C{
   public:
       static int num;

       C(){++num;}
       ~C(){--num;}

       int get_number_objs(){return num;}
};

int C::num = 0;

Does this do the trick?

This looks straightforward and might make sense, but I'm wondering if there are edge cases where you mess around with pointers or something like that where something falls through the cracks.

This is a solution verification more than anything else.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
RedRubber
  • 1
  • 2
  • 1
    Test it with `C c; C d = c; std::cout << d.get_number_objs();`. Or looking at it another way: You are clearly violating the [rule-of-three](https://en.cppreference.com/w/cpp/language/rule_of_three), so something is likely wrong. – user17732522 Nov 16 '22 at 00:18
  • So I would need to overload the copy constructor and the copy assignment constructors as well, correct? – RedRubber Nov 16 '22 at 00:23
  • As well as move constructor and move assignment operator where necessary. Though I believe you may be able to default the assignment operators in most cases. They won't be increasing or decreasing the count. – user4581301 Nov 16 '22 at 00:24
  • @RedRubber only the copy constructor (and move constructor). An assignment operator merely copies/moves member data from an existing instance to another existing instance, it does not create a new instance, only the constructors do that. – Remy Lebeau Nov 16 '22 at 00:26

1 Answers1

3

Does this do the trick?

Almost. You also need to increment num inside of the class's copy constructor, as well as the move constructor in C++11 and later.

Also, there is no point in having get_number_objs() if num is public, but since that does expose num to tampering from outside, num should be private instead. And get_number_objs() should be static.

Try this:

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;

Alternatively, in C++17 and later, you can inline the num variable so you don't have to define it separately outside of the class:

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; }
};
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • This makes sense. I wanted to clarify one thing though- now that we have declared a move constructor, we are obligated to also overload the copy assignment, yes? (In any case, for copy assignment should not increase num). If I'm correct in this, what would happen if we skipped overloading the move assignment entirely? What situation would a failure occur? The examples I can think of where a move constructor comes into play, moved from objects are temporary anyway. – RedRubber Nov 16 '22 at 00:46
  • "*we are obligated to also overload the copy assignment, yes?*" - no, because your class does not have any non-static data members that need to be copied/moved, so the default compiler-generated operators will suffice. In any case, you really need to get in your head the difference between **construction** and **assignment**, they are two different things. – Remy Lebeau Nov 16 '22 at 00:48
  • I thought in C++11 and beyond, [user defined move constructor disables the implicit copy constructor](https://stackoverflow.com/questions/11255027/why-user-defined-move-constructor-disables-the-implicit-copy-constructor). I mean, it's not a problem to define it myself I'm just making sure. – RedRubber Nov 16 '22 at 00:51
  • The class shown does not have an implicitly-generated copy constructor, it has a user-defined copy constructor: `C(const C&)`. In any case, you should read more about [Copy constructors](https://en.cppreference.com/w/cpp/language/copy_constructor), [Copy assignment operator](https://en.cppreference.com/w/cpp/language/copy_assignment), [Move costructors](https://en.cppreference.com/w/cpp/language/move_constructor), and [Move assignment operator](https://en.cppreference.com/w/cpp/language/move_assignment) – Remy Lebeau Nov 16 '22 at 00:53
  • For instance when I copy paste this I get ``note: 'constexpr C& C::operator=(const C&)' is implicitly declared as deleted because 'C' declares a move constructor or move assignment operator``. (To get this I just did ``C a; C b; b = a;``). [Link to what I ran](https://cpp.sh/?source=%2F%2F+Example+program%0A%23include+%3Ciostream%3E%0A%23include+%3Cstring%3E%0A%0Aint+main()%0A%7B%0A++std%3A%3Astring+name%3B%0A++std%3A%3Acout+%3C%3C+%22What+is+your+name%3F+%22%3B%0A++getline+(std%3A%3Acin%2C+name)%3B%0A++std%3A%3Acout+%3C%3C+%22Hello%2C+%22+%3C%3C+name+%3C%3C+%22!%5Cn%22%3B%0A%7D) – RedRubber Nov 16 '22 at 00:57
  • Thanks for the help I'm trying to get my head around all the kinds of constructors – RedRubber Nov 16 '22 at 00:58
  • You are still confusing **construction** with **assignment**. But yes, an *implicit* **copy assignment** operator is `delete`'d if a user-defined **move constructor** is present (an *explicit* user-defined operator is not `delete`'d), so you would have to explicitly declare a user-defined **copy assignment** operator for `b = a;` to work in that case. On a side note: `C b = a;` is actually **construction**, not **assignment**. – Remy Lebeau Nov 16 '22 at 01:03
  • Agreed; I meant copy assignment earlier, not copy construct. Which brings me back to my earlier question if you don't mind - if I were to not define the move constructor myself, and rely on the implicitly defined copy assignment (which should work fine as you said), what situations would result in failure? I've only encountered examples of the move constructor being invoked when temporary objects are called for and quickly disposed of, so I'm not sure if it's actually needed here. So, what when would we fail if we only overloaded copy constructor and nothing else? – RedRubber Nov 16 '22 at 01:06
  • @RedRubber None, if the class is only copyable and not movable. That is the way classes operated before C++11 added move semantics. As long as you follow the [Rule of Three](https://en.cppreference.com/w/cpp/language/rule_of_three) correctly (which became the Rule of Five in C++11), you should be fine. – Remy Lebeau Nov 16 '22 at 01:11
  • The counter should be `std::atomic` to avoid race conditions and overflow. – Ben Nov 16 '22 at 04:07
  • @Ben that is useful only if the class is used in multiple threads. But that won't prevent overflows. And the chances of creating billions of active instances is practically non-existant (std containers would also overflow if that happened) – Remy Lebeau Nov 16 '22 at 15:45
  • By "prevent overflows" I meant "using `unsigned` to count a number of instances is dicy" since `unsigned` is typically uint32 and so runs out at ~4.3B. – Ben Nov 16 '22 at 16:02
  • @Ben There is no size guarantee on `size_t`, other than it can't be less than 16 bits, so it *may* be equivalent to `unsigned short` or `unsigned int`, though it is typically more like `unsigned long long`. It depends on the implementation. But you are right, for an object counter, `size_t` makes more sense than `unsigned`. I have updated my examples. – Remy Lebeau Nov 16 '22 at 17:09
  • Interesting. I’d assumed that `sizeof(std::size_t) == sizeof(void*)` and so `std::size_t` could by definition enumerate every live object of any one type. – Ben Nov 16 '22 at 22:41
  • This seems to agree with me: https://en.cppreference.com/w/cpp/types/size_t if `std::size_t` is 16 bits then can’t a `char[]` be at most 65535 long? – Ben Nov 16 '22 at 22:48
  • @Ben "*`std::size_t` can store the **maximum size** of a theoretically possible object of any type (including array)*" - that has nothing to do with `void*`, which can hold the *address* of an object, not its *size*. – Remy Lebeau Nov 17 '22 at 00:08
  • So are there any actual systems that have more than 65536 bytes of address space but don’t allow buffers bigger than that and so can have 16-bit `size_t`? – Ben Nov 17 '22 at 02:39