в C # у вас нет иерархии множественного наследования, поэтому этот способ работает только для очень простых случаев: представьте классы object-> A-> B, объект хочет добавить некоторую статическую функциональность, а также A, вы определите объект класса <T>, класс A <T> и класс B: объект <B>, A <B> невозможен

ли способ заставить все производные классы считать их экземпляры? Как (написать код на одном из C ++, C #, 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; }
}

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

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

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

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

Помощь высоко ценится.

РЕДАКТИРОВАТЬ: спасибо за хороший ответ, в C ++ возможно также использовать CRTP (Любопытно повторяющийся шаблон шаблона), но не в C # / 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; }
};
 peenut10 янв. 2011 г., 15:43
существует огромная разница в производительности между подходом на основе типа прогона по сравнению с простым приращением / уменьшением для большого проекта, что может означать замедление; однако, это более общий вопрос о внедрении статической функциональности в производные классы ;-)
 Ralph10 янв. 2011 г., 15:02
Что делаешь? - Сколько случаев, по вашему мнению, вы думаете о проблемах производительности «подхода на основе типов во время выполнения»?
 Tim Barrass10 янв. 2011 г., 15:02
+1 за интересный вопрос.

Ответы на вопрос(7)

Я бы использовал шаблон. Это в C ++, кстати.

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 может рассчитывать только самые производные классы по отдельности.

 Puppy10 янв. 2011 г., 15:03
@Steve: Я уверен, что это полностью определяется реализацией, но я также уверен, что все реализации дают уникальные строки.
 peenut10 янв. 2011 г., 15:51
да, нравится то, что реализация C ++ определена, и (часто необходимо) все реализации определяли предположения впоследствии
 Puppy10 янв. 2011 г., 16:36
Я проверил, и похоже, что в MSVC10 type_info предоставляет функцию hash_code () для такого рода целей. Но я не могу сказать, является ли это Standard и нет, и я уверен, что это не C ++ 03.
 Steve Jessop10 янв. 2011 г., 14:44
Подайте обычный аргументtypeinfo::name() гарантированно возвращает различные строки для всех типов. Очевидно, это на практике, но для безопасностиcounts может быть карта изtypeinfo* вintс пользовательским компараторомreturn *lhs < *rhs;

дет работать в 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();
    }
}

Вам не нужно включать свойство количества экземпляров в каждый класс, оно требуется только на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.

 peenut10 янв. 2011 г., 16:01
в C # у вас нет иерархии множественного наследования, поэтому этот способ работает только для очень простых случаев: представьте классы object-> A-> B, объект хочет добавить некоторую статическую функциональность, а также A, вы определите объект класса <T>, класс A <T> и класс B: объект <B>, A <B> невозможен
 peenut10 янв. 2011 г., 14:57
что предостережения (или должны быть) известны разработчику Java / C #, давайте не будем отвлекаться ;-)
 Peter Lillevold10 янв. 2011 г., 15:08
@peenut - я согласен, что это выглядит хуже. Я решил сначала показать этот вариант, так как, по крайней мере, в .Net не всегда есть роскошь создания подклассов.
 Henk Holterman10 янв. 2011 г., 14:48
С некоторыми оговорками, используя деструктор: дорого и поздно.
 peenut10 янв. 2011 г., 14:47
Этот пример означает добавление кода в производный класс, который выглядит намного хуже, чем CRTP-решение для c ++

В 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> если вы не хотите зависимость от гуавы.

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

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;

    }

}
 peenut10 янв. 2011 г., 14:49
да, решение на основе (типа времени выполнения) возможно, однако гораздо медленнее. для подсчета мусора экземпляра используйте финализатор Java ;-)
 finnw10 янв. 2011 г., 15:37
@peenut, я не рекомендую переопределятьfinalize, но я добавил пример, который достигает того же, используяPhantomReference.
 peenut10 янв. 2011 г., 15:53
хорошо, хорошо, есть слабые, мягкие, призрачные и т. д. это было больше для иллюстрации концепции, но вы правы :-)

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>());

Вывод может быть сделан по-другому, может быть. И это не чистый ООП, но и примеры C ++ или Java в этой теме. Но это не требует немного кода в наследовании класса.

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

 Euphoric13 янв. 2011 г., 08:05
Что ж, тогда вы можете реализовать один и тот же кусок кода в каждом производном классе и иметь отличную производительность. Или вы используете мой код и вам не нужно писать код с чуть худшей производительностью. Но пока вы не создаете сотни тысяч классов в секунду, эта разница незначительна. Программирование - это все о компромиссах.
 Euphoric11 янв. 2011 г., 09:20
но гораздо медленнее: вы делали какое-нибудь обширное профилирование производительности? Или стреляете со стороны?
 peenut11 янв. 2011 г., 08:51
как я уже говорил в этом вопросе, подход на основе (типа времени выполнения) возможен в c # / java, но гораздо медленнее; Я могу написать этот код, я не спрашивал об этом
 peenut13 янв. 2011 г., 12:05
вопрос был о лучшем решении, чем map <type, int>, вы не ответили на мой вопрос, просто потратили время - я могу написать это (или подобное), это легко
 peenut13 янв. 2011 г., 07:31
Да ладно, если сравнить целочисленный прирост с поиском ключа карты и целочисленным приращением, это не так уж сложно сравнить.

вном этоявляется 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.

 Puppy10 янв. 2011 г., 15:04
@Serge: О чем ты говоришь? Instance_counter не заменяет плохой дизайн Object. Вы просто наследуете его, если хотите, чтобы ваши экземпляры были подсчитаны - вот и все.
 Mark B10 янв. 2011 г., 14:30
+1 бей меня на минуту.
 peenut10 янв. 2011 г., 14:51
нет, это НЕ миксин, миксин не добавляет базовый класс! вот почему у нас нет миксинов в c ++ и нет миксинов в языках, которые не поддерживают множественное наследование?
 peenut10 янв. 2011 г., 14:59
+1 не реализовал эту возможность (выглядит лучше для c ++), но вопрос остается
 Steve Jessop06 февр. 2011 г., 19:14
@Victor T: защищенный деструктор останавливает кого-то, делающего что-то опасное (удаление через неправильный тип). Защищенный конструктор останавливает кого-то, делающего что-то бессмысленное и глупое (создание экземпляра класса, который ничего не делает для него и который он не может уничтожить). Похоже, хорошая идея, чтобы сделать ctors защищенным тоже. Это просто упало ниже моего порога беспокойства об этом, тогда как я всегда беспокоюсь о деструкторах.

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

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

 Tim Barrass10 янв. 2011 г., 15:02
Извинения - я думал, что вы не хотите добавлять такую ​​функциональность по наследству. Не стесняйтесь игнорировать :)
 peenut10 янв. 2011 г., 15:00
классы связаны / объединены наследованием

ашей библиотеки.

Этот базовый класс содержит Map - связь классов с количеством экземпляров. Если создан экземпляр base или один из его подклассов, вызывается конструктор ins. Конструктор увеличивает количество экземпляров класса 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());
        }
    }
}
 peenut10 янв. 2011 г., 15:58
хороший способ, динамическое изменение класса - другой путь; Тем не менее, вам нужно использовать карту, которая медленнее, но увеличение / уменьшение может быть быстрее (я не тестировал), чем подход Map <Class, AtomaticInteger>? Ты проверял это?
 Ralph10 янв. 2011 г., 17:04
Я не тестировал производительность, но тестировал код. - Я использовал AtomaticInteger и ConcurrentMap в основном из-за проблем с протектором, потому что я не хочу ограничивать использование и не хочу использовать синхронизированные блоки.
 Ralph10 янв. 2011 г., 14:59
@peenut: вы думаете this.getClass (); это медленно? (я пропустил эту часть вашего вопроса)
 peenut10 янв. 2011 г., 14:55
как уже упоминалось в вопросе, существует решение на основе (типа времени выполнения) для Java / C #, но гораздо медленнее -> вопрос не в этом, я могу написать такой код на Java, C #

Ваш ответ на вопрос