7

我想在 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;
};

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

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

谢谢。

4

6 回答 6

6

看看GoF 观察者设计模式

于 2009-04-02T13:54:53.330 回答
3

这将是一个令人讨厌的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's 地址减去相同的偏移量。

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

于 2009-04-02T14:40:16.293 回答
3

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

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

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

于 2009-04-02T15:35:48.807 回答
2

或类似的东西:

从您的通知程序继承并添加 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 )
    {}
};
于 2009-04-02T14:44:29.653 回答
1

部分解决方案是让 Owned 从 Notifier 继承。这样,被破坏对象的地址就是'this'......

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

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

感谢fa的回答,这是我正在寻找的解决方案:

#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;
}

谢谢!

于 2009-04-02T13:48:12.470 回答
0

我对此表示高度怀疑。Notifier 无法知道它已在组合中使用。如果我这样做怎么办

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

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

于 2009-04-02T13:57:50.483 回答