1

I made a base class object to track instances of some objects. I want to log this in a file like so:

Object Name: Number of instances

Here is my base class:

template <class T>
class CountedObj
{
public:
   CountedObj()
   {
        // write name of derived class and counter to log
   }

   CountedObj(const CountedObj& obj)
   {
       // write name of derived class and counter to log
   }

   ~CountedObj() 
   {
       // write name of derived class and counter to log
   }
private:
   int counter;
   mylogger log;
};

My problem is that I want to print the name of the class that will inherit from my CountedObj, but I can't use typeid inside the constructor.

Is there any alternative to log which object is allocated and deallocated ?

Adrian
  • 19,440
  • 34
  • 112
  • 219
  • Names and symbols are only available in the source. Once a [*translation unit*](https://en.wikipedia.org/wiki/Translation_unit_(programming)) have been compiled into an object file and linked into an executable, it basically loses all symbols. C++ have no standard [*introspection*](https://en.wikipedia.org/wiki/Introspection_(computer_science)) or [*reflection*](https://en.wikipedia.org/wiki/Reflection_(computer_programming)) facilities. – Some programmer dude Jul 28 '17 at 07:59
  • 1
    You could implement a `typename()` method in each class that returns a string and call it in the constructor. – idmean Jul 28 '17 at 08:03
  • @idmean For completeness, such a method would want (need?) to be `static`. – TripeHound Jul 28 '17 at 09:03
  • @TripeHound No, actually it'd need to be virtual. – idmean Jul 28 '17 at 09:05
  • @idmean According to [this answer](https://stackoverflow.com/a/962148/2096401) it's not safe to call virtual functions from constructors, but (a) that's quite old and (b) I probably haven't tried so you could be right. – TripeHound Jul 28 '17 at 09:19
  • @TripeHound Right. That wouldn't work directly. – idmean Jul 28 '17 at 09:28
  • 1
    The only solution I know is not particularly good: pass down the name of the type as a "const char *" to the constructor. And you need to store this pointer in the class, as you need to print it at the destructor. – geza Jul 28 '17 at 11:59

2 Answers2

1

You didn't specified how you would use this CountedObj. I assume this:

class Foo: public CountedObj<Foo> {
};

If you derive further from Foo, then your counter is already unable to differentiate between Foo, and its derived classes, so I suppose that you want to print "Foo" as the name for all derived classes. If you do want to differentiate between these classes, then a different solution is needed (I'll delete this answer if that's the case).

(Or maybe you can derive again from CountedObj: class FooDerived: public Foo, public CountedObj<FooDerived> { };, but this way, FooDerived will be counted both as FooDerived and Foo)

So, you can use typeid this way:

template <typename T>
class CountedObj {
    public:
        CountedObj() {
            counter++;
            printf("%s: %d\n", typeid(T).name(), counter);
        }
    private:
        static int counter; // static is needed
};

If you don't like the output from typeid().name(), then you can add a static name-query function into Foo:

template <typename T>
class CountedObj {
    public:
        CountedObj() {
            counter++;
            printf("%s: %d\n", T::name(), counter);
        }

        ...
};

class Foo: public CountedObj<Foo> {
    public:
        static const char *name() {
            return "Foo";
        }
};
geza
  • 28,403
  • 6
  • 61
  • 135
0

EDIT: here is a better and cleaner version

A solution would be to use an unordered_map in your CountedObj. This is an association map between the name of the class and the number of instances.

Then for each class, store the CountedObj and increase the number of instance in the unordered_map.

#include <stdio.h>
#include <iostream>
#include <string>
#include <unordered_map>

class CountedObj
{
public:
   void increase_instance(const std::string& s)
   {
       if (instances.find(s) == instances.end())
           instances[s] = 1;
       else
           instances[s] += 1;
   }

    void pretty_print()
    {
        for(auto& n = instances.begin(); n != instances.end(); ++n)
            std::cout << "Name of the class " << n->first << " : instances =>" << n->second << std::endl;
    }
private:
    std::unordered_map<std::string, unsigned int> instances;
};

class A
{
    public:
        A(CountedObj& o)
            : name("A"),
              obj(o)
        {
            o.increase_instance(name);
        }
        const std::string& name;
    private:
        CountedObj& obj;
};

class B
{
    public:
        B(CountedObj& o)
            : name("B"),
              obj(o)
        {
            o.increase_instance(name);
        }
        const std::string& name;
    private:
        CountedObj& obj;
};


int main()
{
    CountedObj c;
    A a(c), aa(c), aaa(c);
    B b(c), bb(c);
    c.pretty_print();
    return 0;
}

Output of the program

Name of the class A : instances =>3
Name of the class B : instances =>2

If you don't want to store the CounterObj in each class, you can define the class CounterObj as a Singleton.

========

I made a base class object to track instances of some objects.

Do you assume that the objects that you are trying to track are related? (for instance a base class A has several child like B and C). If you want to track the different instanciation it is better to have a unique type to store the objects (like in a vector of A).

To follow the number of instanciation made for each class you need to store a static int (class variable) for each of your trackable class.

In each constructor you increase this variable by 1.

#include <iostream>
#include <vector>
#include <string>

class A
{
    public:
        A(const std::string& n) : name("A")
        {
            instance_num += 1;
            name = n;
        }
        std::string toString()
        {
            return name;
        }
        virtual int instance_num_get()
        {
            return instance_num;
        }
        static int instance_num;
    protected:
        std::string name;
};


class B : public A
{
    public:
        B() 
          : A("B")
        {
            instance_num += 1; 
        }
        int instance_num_get()
        {
            return instance_num;
        }
        static int instance_num;
};


class C : public A
{
    public:
        C()
          : A("C")
        {
            instance_num += 1; 
        }
        int instance_num_get()
        {
            return instance_num;
        }
        static int instance_num;
};

For your CountedObj, I used a vector of A* containing one instanciation of each class (we only need one to access the different class variables).

template<typename T>
class CountedObj
{
    public:
        CountedObj(std::vector<T*>& v)
          : obj_list(v)
        {
        }

        void pretty_print()
        {
            for (auto elt = obj_list.begin(); elt != obj_list.end(); ++elt)
                std::cout << (*elt)->toString() << ": " << (*elt)->instance_num_get() << std::endl;
        }

    private:
       std::vector<T*> obj_list;
};

Using pretty_print method, we display the name of the class and the number of instantiation.

We init the class variables. Here is a working example:

int A::instance_num = 0;
int B::instance_num = 0;
int C::instance_num = 0;

int main(void)
{
    A a("A");
    B b;
    C c;
    std::vector<A*> vec;
    vec.push_back(&a);
    vec.push_back(&b);
    vec.push_back(&c);
    CountedObj<A> count(vec);
    count.pretty_print();
    return 0;
}

which will give

Output:
    A: 3
    B: 1
    C: 1

I think it would be interesting to use the Observer design pattern which is the idea intended in the solution I provided

AlexM
  • 146
  • 5