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

Требования

  1. Я пишу класс под названием RCObject, что расшифровывается как «объект подсчета ссылок»;
  2. Класс RCObject должен быть абстрактным, служащим базовым классом фреймворка (EC ++ 3 Пункт 7);
  3. Создание экземпляров RCObject подклассы в стеке должны быть запрещены (MEC ++ 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
    };
    
  4. ПРОЯСНЕНЫ: Объявление / возврат необработанных указателей на RCObject (и подклассы) должны быть запрещены (К сожалению, я не думаю, что существует практический способ обеспечить его выполнение, то есть вызвать ошибки компиляции, когда пользователи не следуют). См. Пример исходного кода в пункте 3 выше;

  5. Экземпляры RCObject подклассы должны быть клонируемыми так же, как Cloneable на Яве. (MEC ++ 1 Пункт 25);
  6. Подклассы пользователей RCObject должен уметь писать «Фабрика Методы» для своих подклассов. Не должно быть утечки памяти, даже если возвращаемое значение игнорируется (не присваивается переменной). Механизм, который близок к этому autorelease в Objective-C;

    [ ДОБАВЛЕНО: cschwan а также Кос указал, что возвращая «умный указатель наRCObject«достаточно для выполнения требования.]
  7. ПРОЯСНЕНЫ: Экземпляры RCObject Подклассы должны иметь возможность содержаться в соответствующем std:: или же boost:: контейнер. Мне в основном нужнаstd::vector«контейнер»std::set«контейнер и»std::mapкак контейнер. Базовая линия

    intrusive_ptr<RCObject> my_bear = v[10];
    

    а также

    m["John"] = my_bear;
    

    работать как положено;

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

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

  • Я новичок в Увеличение (Ускорение настолько велико, что мне нужно некоторое время, чтобы ознакомиться с ним. Могут существовать готовые решения, но есть большая вероятность, что я не знаю о таком решении);
  • Из соображений производительности я хотел бы использовать 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() и как их реализовать используя Зависимый от аргумента поиск (aka. 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 Method]

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

«Неумная» версия — главная ()

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

«Умная» версия [Incomplete!]

/* 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;
}

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

Подобные вопросы

Рекомендации

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

статьи

  • [ атомное ]: Примеры использования boost::atomic — Boost.org
  • [ CP8394 ]: Умные указатели для повышения вашего кода — CodeProject
    • [ раздел ]: intrusive_ptr — легкий общий указатель
  • [ DrDobbs ]: Базовый класс для объектов с навязчивым подсчетом ссылок в C ++ — Dr. Dobb’s

19

Решение

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

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

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

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

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

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

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

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

2

Другие решения

  1. Создание экземпляров RCObject подклассы в стеке должны быть запрещены ([MEC ++ 1] [mec ++ 1] Item 27);

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

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

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

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

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

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

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

  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.

1

По вопросам рекламы [email protected]