【问题标题】:How can I know the address of owner object in C++?如何知道 C++ 中所有者对象的地址?
【发布时间】:2009-04-02 13:47:53
【问题描述】:

我想在 C++ 中创建一个 Notifier 类,我将在其他对象中使用它来在对象被销毁时通知各个持有者。

template <class Owner>
class Notifier<Owner> {
public:
  Notifier(Owner* owner);
  ~Notifier(); // Notifies the owner that an object is destroyed
};

class Owner;

class Owned {
public:
  Owned(Owner* owner);
private:
  Notifier<Owner> _notifier;
};

我的意思是,由于我有一个密集而复杂的对象图,我想避免将拥有对象的地址存储在通知程序中。有没有办法更改我的通知程序类,以便它可以从自己的地址和在编译时计算的偏移量推断出拥有对象的地址?

另请注意,任何对象都可能必须通知多个“所有者”,可能来自同一类。

谢谢。

【问题讨论】:

  • 您需要静态多态性还是可以使用例如创建抽象基类IOwner纯虚方法'notify'?
  • 不,我不能有一个纯虚拟的“通知”。

标签: c++ templates notifications one-to-many


【解决方案1】:

【讨论】:

  • 我确实在寻找一种实现一对多关系的方法。它可以很好地用于观察者模式,但不一定。
  • @Xavier:我不确定我是否理解你。 “非常适合用于观察者”。观察者是您实现通知的方式。
  • “观察者定义了一对多的关系,这样当一个对象改变状态时,其他对象会被通知并自动更新”。但我希望很多人通知那个人(以一种节省内存的方式)。
【解决方案2】:

这将是一个讨厌的 hack,并且可能无法保证有效,但这里有一个想法我不推荐这个

假设您的布局与您描述的一样:

template <class Owner>
class Notifier<Owner> {
public:
  Notifier(Owner* owner);
  ~Notifier(); // Notifies the owner that an object is destroyed
};

class Owner;

class Owned {
public:
  Owned(Owner* owner);
private:
  Notifier<Owner> _notifier;
};

如果_notifier知道它的名字,它可以像这样计算Owned的地址(在Notifier的构造函数中执行):

Owned *p = reinterpret_cast<Owned *>(reinterpret_cast<char *>(this) - offsetof(Owned, _notifier));

基本上,假设是 _notifier 在 Owned 类中的某个固定偏移量。因此,Owned 的地址等于_notifier 的地址减去相同的偏移量。

再一次,这是我不推荐的未定义行为,但可能会起作用。

【讨论】:

    【解决方案3】:

    fa.'s answer 是一个好的开始。但是,它并不能解决拥有多个相同类型所有者的问题。一种解决方案是让通知程序存储所有者列表而不是单个所有者。这是一个快速实现,以展示这个想法:

    template <typename Owner, typename Owned>
    class Notifier
    {
      protected:
        Notifier()
        {}
    
        // Constructor taking a single owner
        Notifier(Owner & o) 
        { 
            owners.push_back(&o); 
        }
    
        // Constructor taking a range of owners
        template <typename InputIterator>
        Notifier(InputIterator firstOwner, InputIterator lastOwner)
            : owners(firstOwner, lastOwner) {}
    
        ~Notifier()
        {
            OwnerList::const_iterator it = owners.begin();
            OwnerList::const_iterator end = owners.end();
            for ( ; it != end ; ++it)
            {
                (*it)->notify(static_cast<Owned*>(this));
            }
        }
    
        // Method for adding a new owner
        void addOwner(Owner & o) 
        { 
            owners.push_back(&o); 
        }
    
    private:
        typedef std::vector<Owner *> OwnerList;
        OwnerList owners;
    };
    

    你可以这样使用它:

    class Owner;
    
    class Owned : public Notifier<Owner, Owned>
    {
        typedef Notifier<Owner, Owned> base;
    
        //Some possible constructors:
        Owned(Owner & o) : base(o) { }
    
        Owned(Owner & o1, Owner & o2)
        {
            base::addOwner(o1); //qualified call of base::addOwner
            base::addOwner(o2); //in case there are other bases
        }
    
        Owned(std::list<Owner*> lo) : base(lo.begin(), lo.end()) { }
    };
    

    如果您有许多不同类型的所有者,则此解决方案可能会变得相当难以使​​用。在这种情况下,您可能需要查看 boost 元编程库(MPLFusion),您最终可以使用这些代码完成类似的操作:

    class Owned : public Notifier<Owned, OwnerType1, OwnerType1, OwnerType2>
    {
        Owned(OwnerType1 & o1, OwnerType1 & o2, OwnerType2 & o3) 
            : base(o1,o2,o3)
    };
    

    但是,实施此解决方案将比以前的解决方案稍长一些。

    【讨论】:

    • 如果需要动态的所有者列表,这将非常有趣。就我而言,我事先知道每种类型有多少个所有者(通常是一个,有时是两个,永远不会更多)。
    【解决方案4】:

    或者类似的东西:

    从您的通知程序继承并添加 Owned 作为模板参数。然后你可以在通知器中拥有一个可用的方法:

    template < class Owner , class Owned >
    class Notifier
    {
    public:
        Notifier(Owner* owner)
        {}
    
        Owned * owned()
        { return static_cast< Owned * >( this ); }
    
        ~Notifier()
        {
            // notify owner with owned()
        }
    };
    
    class Owner
    {};
    
    class Owned : public Notifier< Owner , Owned >
    {
    public:
        Owned( Owner * owner ) : Notifier< Owner , Owned >( owner )
        {}
    };
    

    【讨论】:

    • +1。这在我看来是最好的解决方案。但是,它不允许同一类型的多个所有者。
    • @Luc:是的,它确实允许同一类型的多个所有者只做更多的工作:添加例如一个 int 模板参数...查看我的答案。
    • 此解决方案仅适用于对象图几乎是静态的(不超过一个相同类型的对象)。如果您以树状方式动态创建实例,则需要存储所有者的实例。
    • 这是奇怪重复的模板模式 (CRTP):c2.com/cgi/wiki?CuriouslyRecurringTemplate
    【解决方案5】:

    部分解决方案是让 Owned 从 Notifier 继承。这样一来,被销毁对象的地址就是简单的 'this'...

    class Owned : public Notifier<Owner> {
    public:
      Owned(Owner* owner) 
        : Notifier<Owner>(owner)
      {}
    };
    

    但是如何处理来自同一类的多个“所有者”?同一个类怎么能继承多次呢?

    感谢fa's answer,这是我正在寻找的解决方案:

    #include <iostream>
    
    template <class Owner, class Owned, int = 0>
    class Notifier {
    public:
      Notifier(Owner* owner)
        : _owner(owner)
      {}
      ~Notifier() {
        _owner->remove(owned());
      }
      Owned * owned(){ 
        return static_cast< Owned * >( this ); 
      }
    
    private:
      Owner* _owner;
    };
    
    class Owner {
    public:
      void remove(void* any) {
        std::cout << any << std::endl;
      }
    };
    
    class Owned : public Notifier<Owner,Owned,1>, Notifier<Owner,Owned,2> {
    public:
      Owned(Owner* owner1, Owner* owner2)
        : Notifier<Owner,Owned,1>(owner1)
        , Notifier<Owner,Owned,2>(owner2)
      {}
    };
    
    int main() {
      std::cout << sizeof(Owned) << std::endl;
      Owner owner1;
      Owner owner2;
      Owned owned(&owner1, &owner2);
      std::cout << "Owned:" << (void*)&owned << std::endl << std::endl;
    }
    

    谢谢!

    【讨论】:

    • 在通知器中,您不能存储所有者列表而不是单个所有者吗?
    • 您的用例真的能让您在编译时了解所有这些吗?而且你对实例化这么多模板的代码膨胀感到满意吗?
    • 用例是在业务对象模型中实现类之间的关系。例如。需求与客户相关联:我对这些类了如指掌,而且我知道每个需求恰好有 1 个客户,每个客户有 0 到 n 个需求...
    【解决方案6】:

    我非常怀疑。 Notifier 无法知道它已在组合中使用。如果我这样做了怎么办

    class Foo
    {
    private:
      Notifier _a, _b, _c;
    }
    

    虽然我很想被证明是错误的,但我真的怀疑如果不向通知者明确提供更多信息,这是否可行。

    【讨论】:

    • 在编译时提供此信息的任何模板(或其他)技巧?
    • 模板无法帮助您。
    猜你喜欢
    • 1970-01-01
    • 2011-05-07
    • 2020-03-31
    • 2015-03-28
    • 1970-01-01
    • 2011-06-28
    • 2011-10-20
    • 2015-02-09
    相关资源
    最近更新 更多