Проблемы с моими шаблонами Rabbit Hole? - PullRequest
2 голосов
/ 01 октября 2009

Сегодня я столкнулся с проблемой, которая казалась настолько распространенной, что я задался вопросом, почему только сейчас я заметил это. Представьте, что у вас есть класс, который вы хотите обработать, используя boost::shared_ptr<>. У этого класса есть члены, которые должны переслать shared_ptr<> s себя другим методам; другими словами, он должен создать shared_ptr<> s для указателя this. Способ сделать это - удержать weak_ptr внутри класса и использовать статический член в качестве фабрики для new экземпляра самого себя, обернуть его внутри shared_ptr<>, настроить член weak_ptr<> и вернуть управляемый shared_ptr<>, согласно указаниям в boost::shared_ptr<> онлайн-документации (http://www.boost.org/doc/libs/1_40_0/libs/smart_ptr/shared_ptr.htm#Introduction):

Поскольку реализация использует подсчет ссылок, циклы экземпляров shared_ptr не будут возвращаться. Например, если main () удерживает shared_ptr в A, который прямо или косвенно хранит shared_ptr обратно в A, счетчик использования A будет равен 2. Уничтожение исходного shared_ptr приведет к зависанию A со счетчиком использования 1. Используйте weak_ptr для «Циклы разрыва».

Проблема заключается в том, что этот класс может быть унаследован, и производные классы имеют то же самое требование иметь weak_ptr<> для себя. Поэтому я подумал об этом паттерне, который я назвал «Кроличья нора», который позволяет каким-то образом сделать что-то эквивалентное «переопределению типа члена». Он работает, имея член, который вы хотите «переопределить» в унаследованных классах внутри шаблона Sonetto::RabbitHole<>:

class Base
{
protected:
    Sonetto::RabbitHole<MemberBaseClass> mMember;
};

Вызов mMember.get() возвращает ссылку типа MemberBaseClass для использования. Если создан базовый класс, mMember.get() действительно вернет экземпляр MemberBaseClass. Но давайте добавим еще один уровень кроличьей норы в уравнение, например:

class Derived : public Base
{
public:
    Derived()
    {
        Base::mMember.link(mMember);
    }

protected:
    Sonetto::RabbitHoleLevel<MemberBaseClass,MemberDerivedClass> mMember;
};

На этот раз мы используем шаблон Sonetto::RabbitHoleLevel<>. Первый параметр шаблона - это тип, который должен быть возвращен предыдущим уровнем кроличьей норы, который в данном случае равен MemberBaseClass. Второй параметр шаблона - это тип, который будет фактически создан, если Derived::mMember - самый глубокий уровень кроличьей норы в объекте. Члены более глубокого уровня, очевидно, должны быть унаследованы от этого типа. Конструкторы отвечают за связывание уровней, как показано во фрагменте. Когда пришло время использовать указатель, каждый класс может безопасно использовать свой собственный требуемый тип:

void Base::doSomething()
{
    MemberBaseClass &ref = mMember.get();
    // [...]
}

void Derived::doSomething()
{
    MemberDerivedClass &ref = mMember.get();
    // [...]
}

// And so on...

Я хотел спросить об этом, чтобы люди помогли мне найти, какие типы схем наследования могут сделать шаблон бесполезным. К тому же, плохая вещь в том, что она добавляет 12 байтов служебной информации для каждого уровня кроличьей норы, поэтому для класса с двумя кроличьими ямами и двумя унаследованными классами будет большая служебная нагрузка в 72 байта. Как вы думаете, такие издержки делают этот класс плохим классом для использования во многих местах моего кода? Код следует:

// SonettoRabbitHole.h
#ifndef SONETTO_RABBITHOLE_H
#define SONETTO_RABBITHOLE_H

#include <cstdlib>

namespace Sonetto
{
    // ---------------------------------------------------------------------------------
    // Sonetto::IHalfTypedRabbitHole declaration
    // ---------------------------------------------------------------------------------
    template <class B>
    class IHalfTypedRabbitHole
    {
    public:
        virtual ~IHalfTypedRabbitHole() {}

        virtual B &get() = 0;
    };

    // ---------------------------------------------------------------------------------
    // Sonetto::RabbitHoleLevel declaration
    // ---------------------------------------------------------------------------------
    template <class B,class T>
    class RabbitHoleLevel : public IHalfTypedRabbitHole<B>
    {
    public:
        inline RabbitHoleLevel();
        virtual ~RabbitHoleLevel();

        template <class _T,class D>
        void link(RabbitHoleLevel<_T,D> &link)
        {
            mNextLevel = &link;
        }

        inline virtual T &get();

    private:
        IHalfTypedRabbitHole<T> *mNextLevel;

        T *mImpl;
    };

    // ---------------------------------------------------------------------------------
    // Sonetto::RabbitHole declaration
    // ---------------------------------------------------------------------------------
    template <class T>
    struct RabbitHole : public RabbitHoleLevel<T,T> {};

    // ---------------------------------------------------------------------------------
    // Sonetto::RabbitHoleLevel template and inline implementations
    // ---------------------------------------------------------------------------------
    template <class B,class T>
    inline RabbitHoleLevel<B,T>::RabbitHoleLevel()
            : mNextLevel(NULL), mImpl(NULL) {}
    // ---------------------------------------------------------------------------------
    template <class B,class T>
    RabbitHoleLevel<B,T>::~RabbitHoleLevel()
    {
        // If we are at the end of the rabbit hole, we
        // delete what we have instantiated here
        if (mNextLevel == NULL)
        {
            delete mImpl;
        }
    }
    // ---------------------------------------------------------------------------------
    template <class B,class T>
    inline T &RabbitHoleLevel<B,T>::get()
    {
        if (mImpl == NULL)
        {
            if (mNextLevel)
            {
                mImpl = &mNextLevel->get();
            }
            else
            {
                mImpl = new T();
            }
        }

        return *mImpl;
    }
    // ---------------------------------------------------------------------------------
}

#endif

// main.cpp to demonstrate usage
#include <iostream>
#include "SonettoRabbitHole.h"

// "Base 'virtual' member" declaration
class MemberReality
{
public:
    // This will be called when you call an instantiated
    // Reality's callme() method
    virtual void callme() const { std::cout << "Member Reality.\n"; }
};

// "Overriden 'virtual' member" declaration
class MemberWonderland : public MemberReality
{
public:
    // This will be called when you call an instantiated
    // Wonderland's Reality::callme() method
    void callme() const { std::cout << "Member Wonderland.\n"; }
};

// Base class with RabbitHole
// Classes inheriting others with rabbit holes can override those holes
// by linking their ones' with the hole of the first class it inherits that
// expresses a level of this hole in question in their constructors; see
// class Wonderland below
class Reality
{
public:
    void callme() // Notice this isn't virtual, but it calls
                  // MemberWonderland in this example: an "overriden member"
    {
        std::cout << "Calling from reality...\n";

        // Access to the deepest hole is granted using the class'
        // rabbit hole member; the first time mRabbitHole.get() is called,
        // the its pointer is instantiated as its deepest linked type
        mRabbitHole.get().callme();
    }

protected:
    Sonetto::RabbitHole<MemberReality> mRabbitHole;
};

// Derived class extending base's rabbit hole
class Wonderland : public Reality
{
public:
    Wonderland()
    {
        // Link previous rabbit hole level with this level
        // Keep in mind that this very Wonderland class could
        // be inherited, so it would be wrong to call
        // mRabbitHole.get() from this constructor, as it would
        // instantiate MemberWonderland when it should have
        // instantiated the class from the next rabbit hole level
        // Because of that, as a rule, you can only access the
        // rabbit hole from a constructor if you are plain sure it
        // will be the deepest level of the hole
        // Rabbit holes work by delaying construction of the objects
        // to the moment they're needed for use (lazy initialization)
        Reality::mRabbitHole.link(mRabbitHole);
    }

protected:
    // The first template parameter is the base pointer type
    // For linkages to work, it must be the same type as the second template
    // parameter passed in the previous level's template
    Sonetto::RabbitHoleLevel<MemberReality,MemberWonderland> mRabbitHole;
};

int main()
{
    Reality r;
    Wonderland w;

    r.callme(); // Prints: 'Member Reality.'

    std::cout << '\n';

    w.callme(); // Prints: 'Member Wonderland.'

    return 0;
}

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

- 2-е редактирование -

Я понимаю, руководствуясь ответами, опубликованными здесь и на основе документации Boost, что я случайно перепутал то, что прочитал в этой документации. Разрешение создания этого weak_ptr<> решает проблему выбора shared_ptr<> для указателя this из конструктора объекта (фактически, из статического метода фабрики). По иронии судьбы мой метод даже не решает эту проблему, поскольку к кроличьей норе можно получить доступ только после полного построения объекта :) Я написал гораздо больше текста, объясняющего другой случай, в котором я мог бы использовать кроличьи норы, но при написании этого я пришел к выводу, что существует гораздо лучший подход с гораздо меньшими абстракциями, который требует только разделения двух классов. Моя мотивация состояла бы в том, что эти два класса имеют большой смысл (скажем, 60% в целом), будучи унаследованными один от другого, но я думаю, что их разделение составляет не менее 40%. Добавляя коэффициент скорости к этим 40%, я думаю, что их просто разделить. Я предполагаю, что кроличьи норы должны будут ждать решения другой проблемы, но они все еще мне нравятся. Я почти уверен, что будут случаи, в которых они будут иметь большой смысл для использования, и, к счастью, я буду точно знать, как реализовать (действительно, у меня будут готовые шаблоны для этого! (:). I позже мне нужно будет выработать исключительную безопасность. Если я сделаю это в ближайшее время, я отредактирую этот пост, чтобы другие, кто посчитал этот шаблон полезным, могли выбрать его готовым к использованию (кстати, я объявляю здесь фрагменты кода опубликовать общественное достояние, не знаю, нужно ли это, но я просто скажу это, смеется).

Спасибо

Ответы [ 2 ]

4 голосов
/ 01 октября 2009

кажется слишком сложным; как насчет:

class Base : public enable_shared_from_this<Base> {
private:
// to get a shared ptr, call shared_from_this()
};


class Derived : public Base {
private:
  shared_ptr<Derived> shared_from_this() {
      return static_pointer_cast<Derived>(Base::shared_from_this());
  }

  shared_ptr<Derived const> shared_from_this() const {
      return static_pointer_cast<Derived const>(Base::shared_from_this());
  }
};

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

3 голосов
/ 01 октября 2009

Возможно, я неправильно понял вашу проблему, но вы смотрели на enable_shared_from_this?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...