Подтвердить что ты не робот

Абстрактный базовый класс для использования с интеллектуальными указателями (intrusive_ptr) - обработка наследования, полиморфизм, клонирование и возврат из методов factory

Требования

  • Я пишу класс под названием RCObject, который означает "Reference Counted Object";
  • Класс RCObject должен быть абстрактным, служащим базовым классом фреймворка (EС++ 3 Пункт 7);
  • Создание экземпляров подклассов RCObject в стеке должно быть запрещено (MEС++ 1 Пункт 27);

    [ ДОБАВЛЕН:]

    [Предположим, что Bear является конкретным подклассом RCObject]

    [C.E. здесь означает Ошибка компиляции]

    Bear b1;                        // Triggers C.E. (by using MEC++1 Item 27)
    Bear* b2;                       // Not allowed but no way to trigger C.E.
    intrusive_ptr<Bear> b3;         // Recommended
    
    Bear* bs1 = new Bear[8];                   // Triggers C.E.
    container< intrusive_ptr<RCObject> > bs2;  // Recommended
    intrusive_ptr_container<RCObject> bs3;     // Recommended
    
    class SomeClass {
    private:
        Bear m_b1;                 // Triggers C.E.
        Bear* m_b2;                // Not allowed but no way to trigger C.E.
        intrusive_ptr<Bear> m_b3;  // Recommended
    };
    
  • CLARIFIED: Объявление/возврат необработанных указателей на RCObject (и подклассы) должно быть запрещено (к сожалению, я не думаю, что существует практический способ его принудительного применения, т.е. запуск ошибки компиляции, когда пользователи не следуют). См. Пример исходного кода в пункте 3 выше;

  • Экземпляры подклассов RCObject должны быть клонированы так же, как Cloneable в Java. (MEС++ 1 Пункт 25);
  • Подклассы пользователей RCObject должны иметь возможность записывать "Factory Методы" для своих подклассов. Не должно быть утечки памяти, даже если возвращаемое значение игнорируется (не назначается переменной). Механизм, близкий к этому, autorelease в Objective-C;

    [ ADDED: cschwan и Кос указал, что возвращение "smart-pointer-to- RCObject" достаточно для выполнения требования. ]

  • CLARIFIED: Экземпляры подклассов RCObject должны содержаться в соответствующем контейнере std:: или boost::. Мне в основном нужен контейнер std::vector -like, контейнер std::set -like и контейнер std::map -like. Базой является то, что

    intrusive_ptr<RCObject> my_bear = v[10];
    

    и

    m["John"] = my_bear;
    

    работает как ожидалось;

  • Исходный код должен быть скомпилирован с использованием компилятора С++ 98 с ограниченной поддержкой С++ 11 (точнее, Visual Studio 2008 и gcc 4.6).

Дополнительная информация

  • Я новичок в Boost (Boost настолько велик, что мне нужно некоторое время, чтобы быть знакомым с ним. существующие готовые решения, но у него есть высокая вероятность, что я не знаю такого решения);
  • В связи с соображениями производительности я хотел бы использовать intrusive_ptr вместо shared_ptr, но я открыт для них обоих и даже любых других предложений;
  • Я не знаю, make_shared(), allocate_shared(), enable_shared_from_this() может помочь (кстати, enable_shared_from_this(), по-видимому, не очень продвинут в Boost - его даже не найти в умный указатель главная страница);
  • Я слышал о "написании пользовательских распределителей", но я боюсь, что это слишком сложно;
  • Интересно, следует ли RCObject унаследовать от boost::noncopyable конфиденциально;
  • Я не смог найти существующую реализацию, которая удовлетворяет всем моим требованиям;
  • Рамка - это игровой движок;
  • Основными целевыми платформами являются Android и iOS;
  • Я знаю intrusive_ptr_add_ref() и intrusive_ptr_release() и как их реализовать, используя Аргумент-зависимый поиск (например, Koenig Lookup);
  • Я знаю, как использовать boost::atomic_size_t с помощью boost:intrusive_ptr.

Определения классов

namespace zoo {
    class RCObject { ... };                  // Abstract
    class Animal : public RCObject { ... };  // Abstract
    class Bear : public Animal { ... };      // Concrete
    class Panda : public Bear { ... };       // Concrete
}

"Неинтеллектуальная" версия - createAnimal() [Factory Метод]

zoo::Animal* createAnimal(bool isFacingExtinction, bool isBlackAndWhite) {
    // I wish I could call result->autorelease() at the end...
    zoo::Animal* result;

    if (isFacingExtinction) {
        if (isBlackAndWhite) {
            result = new Panda;
        } else {
            result = new Bear;
        }
    } else {
        result = 0;
    }

    return result;
}

"Неинтеллектуальная" версия - main()

int main() {
    // Part 1 - Construction
    zoo::RCObject* object1 = new zoo::Bear;
    zoo::RCObject* object2 = new zoo::Panda;
    zoo::Animal* animal1 = new zoo::Bear;
    zoo::Animal* animal2 = new zoo::Panda;
    zoo::Bear* bear1 = new zoo::Bear;
    zoo::Bear* bear2 = new zoo::Panda;
    //zoo::Panda* panda1 = new zoo::Bear;  // Should fail
    zoo::Panda* panda2 = new zoo::Panda;

    // Creating instances of RCObject on the stack should fail by following
    // the method described in the book MEC++1 Item 27.
    //
    //zoo::Bear b;                         // Should fail
    //zoo::Panda p;                        // Should fail

    // Part 2 - Object Assignment
    *object1 = *animal1;
    *object1 = *bear1;
    *object1 = *bear2;
    //*bear1 = *animal1;                   // Should fail

    // Part 3 - Cloning
    object1 = object2->clone();
    object1 = animal1->clone();
    object1 = animal2->clone();
    //bear1 = animal1->clone();            // Should fail

    return 0;
}

"Умная" версия [Неполная!]

/* TODO: How to write the Factory Method? What should be returned? */

#include <boost/intrusive_ptr.hpp>

int main() {
    // Part 1 - Construction
    boost::intrusive_ptr<zoo::RCObject> object1(new zoo::Bear);
    boost::intrusive_ptr<zoo::RCObject> object2(new zoo::Panda);
    /* ... Skip (similar statements) ... */
    //boost::intrusive_ptr<zoo::Panda> panda1(new zoo::Bear); // Should fail
    boost::intrusive_ptr<zoo::Panda> panda2(new zoo::Panda);

    // Creating instances of RCObject on the stack should fail by following
    // the method described in the book MEC++1 Item 27. Unfortunately, there
    // doesn't exist a way to ban the user from declaring a raw pointer to
    // RCObject (and subclasses), all it relies is self discipline...
    //
    //zoo::Bear b;                         // Should fail
    //zoo::Panda p;                        // Should fail
    //zoo::Bear* pb;                       // No way to ban this
    //zoo::Panda* pp;                      // No way to ban this

    // Part 2 - Object Assignment
    /* ... Skip (exactly the same as "non-smart") ... */

    // Part 3 - Cloning
    /* TODO: How to write this? */

    return 0;
}

В приведенном выше коде ( "Умная версия" ) отображается ожидаемый шаблон использования. Я не уверен, соответствует ли эта модель использования лучшим практикам использования интеллектуальных указателей или нет. Пожалуйста, поправьте меня, если это не так.

Похожие вопросы

Ссылки

  • [EС++ 3]: Эффективный С++: 55 конкретных способов улучшить ваши программы и разработки (3-е издание) Скотта Мейера
    • Пункт 7: объявить деструкторы виртуальными в полиморфных базовых классах

  • [MEС++ 1]: более эффективный С++: 35 новых способов улучшить ваши программы и проекты (1-е издание) Скотта Майера
    • Пункт 25: Виртуализация конструкторов и функции, не являющиеся членами.
    • Пункт 27: Требование или запрещение объектов на основе кучи.

Статьи

  • [atomic]: Примеры использования boost::atomic - Boost.org

  • [CP8394]: Умные указатели для повышения вашего кода - CodeProject
    • [раздел]: intrusive_ptr - легкий общий указатель

  • [DrDobbs]: базовый класс для объектов с интуитивно ориентированными ссылками на С++ - Dr. Dobb's
    • [page1]: Страница 1
    • [page2]: Страница 2
    • [page3]: Страница 3
4b9b3361

Ответ 1

make_shared создает экземпляр вашего класса в том же блоке распределения, что и контрольный счетчик. Я не уверен, почему вы думаете, что intrusive_ptr будет иметь лучшую производительность: это здорово, когда уже есть механизм ссылок, который вы не можете удалить, но здесь это не так.

Для клона я бы реализовал его как свободную функцию, которая берет интеллектуальный pojnter и возвращает то же самое. Это друг, и он вызывает частный метод чистого виртуального клонирования в базе, который возвращает общий указатель на базу, а затем быстрый интеллектуальный указатель, переданный в общий указатель на производный. Если вы предпочитаете клонировать как метод, используйте crtp, чтобы дублировать это (предоставление частному клону имени типа secret_clone). Это дает вам ковариантные типы возвращаемых интеллектуальных указателей с небольшими накладными расходами.

Crtp с диапазоном базовых классов часто включает в себя как базовый, так и производный классы. Класс crtp получается из базы и имеет обычный self(), который возвращает производные.

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

Если вы полностью параноик, вы можете заблокировать большинство способов получения необработанного указателя или ссылки на ваш класс: заблокировать оператор * на интеллектуальном указателе. Тогда единственный путь к необработанному классу - это явный вызов operator->.

Другой подход к рассмотрению - unique_ptr и ссылки на него. Вам нужно совместное владение и пожизненное управление? Это упрощает некоторые проблемы (совместное владение).

Обратите внимание, что оборванные слабые указатели препятствуют тому, чтобы память из общего ресурса могла быть возвращена.

Серьезным недостатком всегда использования интеллектуальных указателей является то, что вы не можете иметь экземпляры стека или экземпляры непосредственно внутри контейнеров. Оба они могут быть серьезными достижениями.

Ответ 2

  • Создание экземпляров подклассов RCObject в стеке должно быть запрещено ([MEС++ 1] [meС++ 1] Пункт 27);

Какое ваше обоснование? MEС++ дает пример "объектов, способных совершить самоубийство", что может иметь смысл в контексте игровой среды. Это тот случай?

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

Обратите внимание, что в этом случае вы, вероятно, захотите также запретить создание массивов таких объектов в стеке с помощью new[] - это также предотвращает удаление одного. Вероятно, вы также захотите запретить использование RCObjects в качестве под-объектов (членов в других классах). Это означало бы, что вы запрещаете использование значений RCObject в целом и пусть клиентский код обрабатывает их только с помощью интеллектуальных указателей.

  • Следует избегать объявления/возврата необработанных указателей на RCObject (и подклассы) (к сожалению, я не думаю, что существует способ обеспечить его выполнение путем выпуска ошибок компиляции);

Затем вы должны иметь слабые указатели, чтобы иметь способ сказать: "Я заинтересован в этом объекте, но я не поддерживаю его".

  • Подклассы пользователей RCObject должны иметь возможность писать [ "Factory Методы" ] [factory_method] для своих подклассов. Не должно быть утечки памяти, даже если возвращаемое значение игнорируется (не назначается переменной).

Такая функция вернет временный объект интеллектуального указателя со ссылочным числом, равным 1. Если это временное значение не используется для инициализации другого (таким образом, дальнейшее увеличение количества ссылок), он очистит объект вверх. Вы в безопасности.

  • Экземпляры подклассов RCObject должны содержаться в контейнере std:: или boost:: (или что-то подходящее). Мне в основном нужно что-то похожее на std::vector, std::set и std::map;

Этот вид не согласуется с (3). Если вы настаиваете на том, чтобы объекты были созданы индивидуально в куче и проходили через интеллектуальные указатели (а не как значения), тогда вы также должны использовать контейнеры интеллектуальных указателей.

  • Из-за соображений производительности я хотел бы использовать [ intrusive_ptr] [intrusive_ptr] вместо [shared_ptr] [shared_ptr], но я открыт для них обоих и даже для любых других предложений;

Вы не оптимизируете преждевременно?

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

  • Интересно, следует ли RCObject наследоваться от [boost::noncopyable] [noncopyable] конфиденциально;

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