Подсчитывать экземпляры класса для каждого производного класса

Есть ли способ заставить все производные классы считать свои экземпляры? Как (писать код в одном из С++, С#, Java)?

Представьте, что у меня есть доступ к корневому классу (например, к объекту), и каждый другой класс (прямо или косвенно), полученный из этого класса. Я хочу:

AnyDerivedClass.InstancesCount()

Проблема заключается в том, что нужно отслеживать счет в статической переменной, но невозможно "вводить" статическую переменную в производный класс из базового класса, это относится только к переменным-членам. То есть, я должен написать что-то вроде:

class object 
{ 
 private static int count = 0; 
 protected object() { ++count; }
 protected ~object() { --count; } 
 public static InstancesCount() { return count; } 
};

class derived : object 
{
 private static int count = 0;
 public derived() { ++count; }
 public ~derived() { --count; }
 public static InstancesCount() { return count; }
}

Эта функциональность явно повторяется, и я не могу поместить ее в базовый класс. Обратите внимание, что есть два способа вычисления: если есть 7 экземпляров класса производных 1 и 8 экземпляров класса производных2, существует (а) 15 экземпляров объекта или (b) 0 экземпляров объекта. Меня не волнует, какой из них, потому что я не могу этого сделать (используя разумно практические средства, например, представить 100 классов, половина из которых находится в библиотеке, которую я не могу изменить).

Конечно, теоретически возможно создание карты (некоторый идентификатор типа запуска) = > int count и использование уродливого, медленного (на основе времени) подхода (по крайней мере, на С#, Java).

Конечно, если я могу модифицировать производные классы, я могу использовать copy-paste (ужасный), макрос (да, я знаю), mixins (не на этих языках) и т.д. Но это по-прежнему очень уродливо.

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

Помогите оценить.

EDIT: спасибо за хороший ответ, на С++ возможно использовать CRTP (Curiously recurring template pattern), но не в С#/Java (без множественного наследования). Конечно, нужно иметь доступ к производным классам и добавлять этот базовый класс, поэтому вопрос остается (в противном случае это выглядит лучше).

РЕДАКТИРОВАТЬ 2: выглядит невозможно с текущими языками. статическая часть каждого класса не наследует (и это право), но нет никакого наследующего синглтона, связанного с каждым классом, поэтому такие проблемы не могут быть решены так элегантно. Чтобы проиллюстрировать это, посмотрите на следующий код: обычные члены и статические члены - это текущая функция языков ООП, "одиночный" (или любое другое слово) - это мое предложение/желание:

class Base
{
    static int sMemberBase;
    int memberBase;

    //my wish (note that virtual for methods is allowed!):
    singleton int singletonMemberBase;
};
class Derived : Base
{
    static int sMemberDerived;
    int memberDerived;

    //my wish (note that virtual for methods is allowed!):
    singleton int singletonMemberDerived;
};

//taken apart: (note: XYZStatic classes do not derive)
class Base { int memberBase; }
class BaseStatic { int sMemberBase; } BaseStaticInstance;
class Derived : Base { int memberDerived; }
class DerivedStatic { int sMemberDerived;  } BaseStaticInstance;
//note: Derived::sMemberBase is compile-time changed to Base::sMemberBase

//my wish: (note inheritance!)
class BaseSingleton { int singletonMemberBase; } BaseSingletonInstance;
class DerivedSingleton : BaseSingleton { int singletonMemberDerived; } DerivedSingletonInstance;

Если что-то подобное будет присутствовать на языке, решение для моего вопроса будет простым и изящным:

//with singleton members, I could write counter like this:
class object
{
    singleton int count;
    object() { ++count; }
    ~object() { --count; }
};

Ответ 1

В С++ вы можете сделать это с базовым классом шаблона. В основном это mixin, поэтому он все равно требует, чтобы каждый класс сотрудничал, наследуя от mixin:

// warning: not thread-safe
template <typename T>
class instance_counter {
  public:
    static size_t InstancesCount() { return count(); }
    instance_counter() { count() += 1; }
    instance_counter(const instance_counter&) { count() += 1; }
    // rare case where we don't need to implement the copy assignment operator.
  protected:
    ~instance_counter() { count() -= 1; }
  private:
    static size_t &count {
        static size_t counter = 0;
        return counter;
    }
};

class my_class: public instance_counter<my_class> {};

Поскольку каждый класс с использованием шаблона имеет другой базовый класс, он имеет другую функцию count и, следовательно, другую копию статической переменной counter.

Трюк наследования из класса шаблона, который создается с использованием производного класса в качестве параметра шаблона, называется CRTP.

Ответ 2

В Java вы можете использовать глобальный Multiset:

import com.google.common.collect.ConcurrentHashMultiset;

public abstract class InstanceCounted {

    protected InstanceCounted() {
        COUNT_MAP.add(this.getClass());
    }

    protected static final ConcurrentHashMultiset<Class<? extends InstanceCounted>> COUNT_MAP =
        ConcurrentHashMultiset.create();

}

В качестве альтернативы вы можете использовать Map<Class, Integer>, если вам не нужна зависимость от guava.

Примечание: это только отслеживает создание экземпляра, а не сбор мусора, поэтому счетчик никогда не будет уменьшаться. Вы также можете отслеживать сбор, используя PhantomReference, если вы готовы принять удар производительности:

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;

public abstract class InstanceCounted {

    public static int getInstanceCount(Class<? extends InstanceCounted> clazz) {
        reap();
        return INSTANCES.get(clazz).size();
    }

    protected InstanceCounted() {
        reap();
        INSTANCES.put(getClass(), new CountingReference(this));
    }

    static final Multimap<Class<? extends InstanceCounted>, CountingReference> INSTANCES =
        Multimaps.synchronizedSetMultimap(HashMultimap.<Class<? extends InstanceCounted>, CountingReference>create());

    static final ReferenceQueue<InstanceCounted> QUEUE =
        new ReferenceQueue<InstanceCounted>();

    private static void reap() {
        Reference<? extends InstanceCounted> ref;
        while ((ref = QUEUE.poll()) != null) {
            ((CountingReference) ref).clear();
        }
    }

    private static class CountingReference extends PhantomReference<InstanceCounted> {

        public void clear() {
            super.clear();
            INSTANCES.remove(clazz, this);
        }

        CountingReference(InstanceCounted instance) {
            super(instance, QUEUE);
            this.clazz = instance.getClass();
        }

        private final Class<? extends InstanceCounted> clazz;

    }

}

Ответ 3

Я бы использовал шаблон. Кстати, это на С++.

template<typename T> class object {
private:
    static int count;
public:
    object() { count++; }
    object(const object&) { count++; }
    ~object() { count--; }
    static int GetCount() { return count; }
};
template<typename T> int object<T>::count = 0;

Решение RTTI:

class object {
    static std::map<std::string, int> counts;
public:
    object() { counts[typeid(*this).name()]++; }
    object(const object&) { counts[typeid(*this).name()]++; }
    ~object() { counts[typeid(*this).name()]--; }
    template<typename T> int GetObjectsOfType() {
        return counts[typeid(T).name()];
    }
    int GetObjectsOfType(std::string type) {
        return counts[type];
    }
};
std::map<std::string, int> object::counts;

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

Ответ 4

В Java вы можете реализовать функцию подсчета для общего суперкласса вашего хирачи.

Этот базовый класс содержит карту - сопоставление классов с числом экземпляров. Если создается экземпляр базы или один из ее подклассов, то вызывается конструктор. Конструктор увеличивает количество экземпляров класса concreate.

import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

public class Base {

    /** Threadsave counter */
    private static final ConcurrentMap<Class<? extends Base>, AtomicInteger>
       instancesByClass 
       = new ConcurrentHashMap<Class<? extends Base>, AtomicInteger>(
            10);

    /** The only one constructor of base */
    public Base() {
        Class<? extends Base> concreateClass = this.getClass();
        AtomicInteger oldValue = instancesByClass.putIfAbsent(concreateClass,
                new AtomicInteger(1));
        if (oldValue != null) {
            oldValue.incrementAndGet();
        }
    }

    /* DEMO starts here */
    public static class SubA extends Base{
    }

    public static class SubB extends Base{
    }

    public static class SubSubA extends SubA{
    }


    public static void main(String[] args) {
        printNumbers();
        new SubA();
        new SubA();

        new SubB();

        new SubSubA();

        printNumbers();
    }

    private static void printNumbers() {
        // not thread save!
        for (Entry<Class<? extends Base>, AtomicInteger> item : instancesByClass
                .entrySet()) {
            System.out.println(item.getKey().getName() + "  :  "
                    + item.getValue());
        }
    }
}

Ответ 5

В .Net для достижения этого можно использовать дженерики. Следующая технология не будет работать на Java из-за типа стирания.

public static class InstanceCounter<T>
{
    private static int _counter;

    public static int Count { get { return _counter; }}

    public static void Increase()
    {
        _counter++;
    }

    public static void Decrease()
    {
        _counter--;
    }
}

Теперь в ваших классах, будь то базовые или подклассы, используйте его следующим образом:

public class SomeClass
{
    public SomeClass()
    {
        InstanceCounter<SomeClass>.Increase();        
    }

    ~SomeClass()
    {
        InstanceCounter<SomeClass>.Decrease();
    }
}

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

int someClassCount = InstanceCounter<SomeClass>.Count;

Примечание: этот пример не требует, чтобы классы наследовали класс экземпляра экземпляра.

Если можно позволить сжечь ограничение одного суперкласса в .Net, то также будет работать следующее:

public class InstanceCounter<T>
{
    private static int _counter;

    public static int Count { get { return _counter; }}

    protected InstanceCounter<T>()
    {
        _counter++;
    }

    ~InstanceCounter<T>()
    {
        _counter--;
    }
}

public class SomeClass : InstanceCounter<SomeClass>
{
}

Затем возвращаем счетчик:

int someClassCount = InstanceCounter<SomeClass>.Count;

или

int someClassCount = SomeClass.Count;

Примечание2. Как упоминалось в комментариях, использование финализатора (~SomeClass) медленное и уменьшает счетчик только тогда, когда экземпляр действительно собирается GC. Чтобы обойти это, нужно было бы ввести детерминированное "освобождение" экземпляров, например. реализуя IDisposable.

Ответ 6

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

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

Ответ 7

Путь в С#, который возник в моем уме сразу:

class A : IDisposable
{
    static Dictionary<Type, int> _typeCounts = new Dictionary<Type, int>();
    private bool _disposed = false;

    public static int GetCount<T>() where T:A
    {
        if (!_typeCounts.ContainsKey(typeof(T))) return 0;

        return _typeCounts[typeof(T)];
    }

    public A()
    {
        Increment();
    }

    private void Increment()
    {
        var type = this.GetType();
        if (!_typeCounts.ContainsKey(type)) _typeCounts[type] = 0;
        _typeCounts[type]++;
    }

    private void Decrement()
    {
        var type = this.GetType();
        _typeCounts[type]--;            
    }

    ~A()
    {
        if (!_disposed) Decrement();
    }

    public void Dispose()
    {
        _disposed = true;
        Decrement();
    }
}

class B : A
{

}

И как его использовать:

        A a1 = new A();
        Console.WriteLine(A.GetCount<A>());
        A a2 = new A();
        Console.WriteLine(A.GetCount<A>());            

        using(B b1 = new B())
        {
            Console.WriteLine(B.GetCount<B>());
        }
        Console.WriteLine(B.GetCount<B>());

Выход может быть выполнен по-другому. И это не чистый OOP, но и примеры С++ или Java в этом потоке. Но он не требует немного кода в наследующем классе.

И не забудьте о правильном удалении ваших объектов!