【问题标题】:count class instances for every derived class计算每个派生类的类实例
【发布时间】:2011-06-06 13:05:21
【问题描述】:

有没有办法让所有派生类计算它们的实例?如何(用 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; }
}

这个功能显然是重复的,我不能把它放在基类中。 注意有两种计算方法:如果有 7 个类derived1 实例和8 个类derived2 实例,则有(a) 15 个对象实例或(b) 0 个对象实例。我不在乎哪一个,因为我都做不到(使用合理实用的方法,例如想象 100 个类,其中一半在我无法修改的库中)。

当然,理论上可以创建 (some run-type type identifier) => int count 的映射,并使用丑陋、缓慢、基于 (run-time type) 的方法(至少在 C#、Java 中) )。

当然,如果我可以修改派生类,我可以使用复制粘贴(糟糕)、宏(是的,我知道)、混合(不是这些语言)等。但这仍然很丑。

这是一个特定的问题,但它发生在我身上好几次,我希望能够将静态成员“注入”到派生类中以优雅地解决问题。

非常感谢您的帮助。

编辑:感谢您的好回答,在 C++ 中也可能使用 CRTP(Curiously recurring template pattern),但在 C#/Java 中则不行(没有多重继承)。当然,必须有权访问派生类并添加这个基类,所以问题仍然存在(如果没有其他方法,这看起来最好)。

编辑 2:使用当前语言看起来是不可能的。每个类的静态部分都不是继承的(没错),但是没有与每个类关联的继承单例,所以这类问题不能这么优雅地解决。 为了说明问题,请看以下代码:普通成员和静态成员是当前的 OOP 语言功能,“单例”(或任何单词)成员是我的建议/愿望:

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 表示一个有趣的问题。
  • 与简单的增量/减量相比,“基于运行类型”的方法之间存在巨大的性能差异,对于可能意味着减速的大型项目;但是,关于将静态功能注入派生类也是更通用的问题 ;-)

标签: c# java c++ oop inheritance


【解决方案1】:

在 .Net 中,可以使用泛型来实现这一点。由于type erasure,以下技术在 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:在cmets中提到,使用终结器(~SomeClass)很慢,只有当实例被GC实际收集时才会减少计数器。为了解决这个问题,必须引入实例的确定性“释放”,例如实现IDisposable

【讨论】:

  • 这个样例是向派生类添加代码,看起来比c++的CRTP方案差很多
  • 使用析构函数有一些注意事项:昂贵且延迟。
  • Java/C# 开发人员知道(或应该知道)警告,我们不要走神 ;-)
  • @peenut - 我同意它看起来更糟。我选择首先展示这个变体,因为至少在 .Net 中,并不总是拥有子类化的奢侈。
  • 在C#中你没有多重继承层次结构,所以这种方式只适用于非常简单的情况:想象类object->A->B,object想要添加一些静态功能,以及A,您定义类 object、class A 和 class B:object、A 是不可能的
【解决方案2】:

立即在我脑海中闪现的 C# 方式:

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,也不是该线程中的 C++ 或 Java 示例。但是继承类不需要一点代码。

别忘了正确处理你的对象!!

【讨论】:

  • 正如我在问题中所说,基于(运行时类型)的方法在 c#/java 中是可能的,但要慢得多;我可以写这段代码,我没问过这个
  • 但慢得多:您是否进行了任何广泛的性能分析?还是从侧面拍摄?
  • 来吧,如果你将整数增量与映射键查找和整数增量进行比较,也不是很难比较。
  • 那么,您可以在每个派生类中实现相同的代码,并获得完美的性能。或者您使用我的代码,而不必编写任何性能稍差的代码。但只要你不每秒创建数十万个类,这种差异可以忽略不计。编程就是妥协。
  • 问题是关于比地图更好的解决方案,你没有回答我的问题,只是浪费时间 - 我可以写这个(或类似的),很容易
【解决方案3】:

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

}

如果您不想依赖 guava,也可以使用 Map&lt;Class, Integer&gt;

注意:这只跟踪实例创建,而不是垃圾回收,因此计数永远不会减少。如果您愿意降低性能,也可以使用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;

    }

}

【讨论】:

  • 是的,基于(运行时类型)的解决方案是可能的,但是要慢得多。要计算实例垃圾,请使用 Java 的终结器 ;-)
  • @peenut,我不建议覆盖finalize,但我添加了一个使用PhantomReference 实现相同目的的示例。
  • ok,ok,有weak,soft,phantom等。这更多是为了说明概念,但你是对的:-)
【解决方案4】:

我会使用模板。顺便说一下,这是用 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 只能单独计算最多派生类。

【讨论】:

  • 提示 typeinfo::name() 是否保证为所有类型返回不同的字符串。显然它在实践中确实如此,但为了安全起见counts 可能是从typeinfo*int 的映射,带有自定义比较器return *lhs &lt; *rhs;
  • @Steve:很确定它完全是由实现定义的,但我也很确定所有实现都会给出唯一的字符串。
  • 是的,喜欢 C++ 的实现定义,并且(通常是必要的)所有实现定义的假设之后
  • 我查了一下,似乎在MSVC10中,type_info为此提供了hash_code()函数。但我不能说这是否是标准的,我很确定它不是 C++03。
【解决方案5】:

令我震惊的是,如果您真的希望注入的/任何类真正做一些有用的事情,那么您必须以某种方式将它与原始类耦合,无论是通过继承还是直接耦合与方法调用。否则你只是有两个独立旋转的轮子。

我能想到的唯一替代方法是使用可以为您计算创建的工厂模式 - 但您必须破解一些东西来计算递减,例如明确地将对象交回工厂。

【讨论】:

  • 类通过继承耦合/连接
  • 抱歉——我以为你不想通过继承来添加这种功能。随意忽略:)
【解决方案6】:

在 Java 中,您可以对 hiracy 的公共超类实现计数功能。

这个基类包含一个 Map - 将类与实例的数量相关联。 如果创建了基类或其子类之一的实例,则调用构造函数。 构造函数增加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());
        }
    }
}

【讨论】:

  • 如问题所述,Java/C# 有基于(运行时类型)的解决方案,但速度要慢得多 -> 问题与此无关,我可以用 Java 编写这种代码, C#
  • @peenut:你认为 this.getClass();是慢? (我错过了你问题的这一部分)
  • 不错的方式,动态类变化是另一种方式;但是,您必须使用更慢的 map,但递增/递减可能比 Map 方法更快(我没有测试)?你测试了吗?
  • 我没有做性能测试,但是我测试了代码。 -- 我使用AtomaticInteger和ConcurrentMap主要是因为踩踏问题,因为我不想限制使用,也不想使用同步块。
【解决方案7】:

在 C++ 中,您可以使用模板基类来实现。基本上它一个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。

【讨论】:

  • 不,它不是 mixin,mixin 不添加基类!这就是为什么我们在 c++ 中没有 mixins,而在不支持多重继承的语言中使用 mixins?
  • +1 没有意识到这种可能性(看起来对于 c++ 来说是最好的),但问题仍然存在
  • @Serge:你在说什么? instance_counter 并不能取代 Object 的糟糕设计。如果你想计算你的实例,你只需从它继承——就是这样。
  • @peenut:“接口”也不添加基类,至少在 Java 中没有。但是在 C++ 中,接口是使用基类实现的。无论如何,你怎么称呼它并不重要,C++ 有一个静态版本的机制,mixin 和猴子补丁可以在其他语言中执行,有时是动态的。也就是说,它们将成员函数添加到类中,并且这些函数可以使用它们添加到的类的属性。该机制是 CRTP(又是基类,有点扭曲)。在这个简单的例子中,我们使用的类的唯一属性就是它的标识。
  • @Victor T:受保护的析构函数阻止某人做一些危险的事情(通过错误的类型删除)。受保护的构造函数阻止某人做一些毫无意义和愚蠢的事情(创建一个对他们没有任何作用的类的实例,并且他们不能破坏)。让 ctors 也受到保护听起来是个好主意。它刚刚低于我担心它的门槛,而我总是担心析构函数。
猜你喜欢
  • 1970-01-01
  • 2021-10-08
  • 2011-04-14
  • 2014-04-27
  • 2023-03-19
  • 1970-01-01
  • 2013-02-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多