Std :: auto_ptr или boost :: shared_ptr для идиомы pImpl?

При использовании pImpl идиома предпочтительнее использовать boost:shared_ptr вместо std::auto_ptr? Я уверен, что когда-то читал, что повышенная версия более дружелюбна к исключениям?

class Foo
{
public:
    Foo();
private:
    struct impl;
    std::auto_ptr<impl> impl_;
};

class Foo
{
public:
    Foo();
private:
    struct impl;
    boost::shared_ptr<impl> impl_;
};

[РЕДАКТИРОВАТЬ] Всегда ли безопасно использовать std :: auto_ptr <> или бывают ситуации, когда требуется альтернативный интеллектуальный указатель ускорения?

Что-то меня действительно беспокоит: вы готовы использовать идиому pimpl - брандмауэр компилятора. Итак, вы пытаетесь ограничить зависимости времени компиляции. Тем не менее, вы с радостью вводите реализацию интеллектуального указателя (scoped_ptr, shared_ptr, auto_ptr или что-то еще), в то время как ваш класс «оболочки» ТОЛЬКО пересылает вызовы классу реализации и удаляет указатель в его деструкторе. Почему-то мне кажется, что если класс оболочки делает что-то еще, дизайн может быть нарушен.

Gregory Pakosz 07.12.2010 19:07

Я считаю, что это заслуживает обновления для нового C++. Херб Саттер недавно блоггинг об этом.

Björn Pollex 18.11.2011 14:04

Аналогичный вопрос: stackoverflow.com/questions/5576922/…

Rolf Kristensen 22.11.2011 02:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
16
3
9 803
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Не пытайся так сильно прострелить себе ногу, в C++ у тебя масса возможностей :) Нет никакой реальной необходимости использовать автоматические указатели, поскольку вы прекрасно знаете, когда ваш объект должен входить и выходить из жизни (в вашем конструкторе (ах) и деструкторе).

Будь проще.

Сильно не согласен. Этот подход не защищает от исключений, вызванных самим ctor, которые могут произойти после создания вашего объекта impl. Когда это происходит, dtor не вызывается, и происходит утечка вашего объекта impl.
Chris Jester-Young 22.11.2008 13:24

Если вы используете идиому pimpl, вполне вероятно, что указатель impl является единственным указателем в классе. Использование неуправляемых указателей в этом случае не вызывает проблем, если деструктор удаляет указатель. Утечка ресурсов в конструкторе невозможна, потому что только с одним членом только new может генерировать и не выделяет память.

daramarak 05.05.2010 19:52

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

CashCow 09.03.2011 19:44

Я обычно использую auto_ptr. Обязательно сделайте свой класс не копируемым (объявите частную копию ctor & operator =, иначе унаследуйте boost::noncopyable). Если вы используете auto_ptr, одна проблема заключается в том, что вам нужно определить не встроенный деструктор, даже если тело пусто. (Это потому, что если вы позволите компилятору сгенерировать деструктор по умолчанию, impl будет неполным типом при генерации вызова delete impl_, вызывая неопределенное поведение).

Выбирать между auto_ptr и указателями ускорения практически некуда. Я стараюсь не использовать boost по стилистическим соображениям, если подойдет стандартная альтернатива библиотеки.

Это напоминает мне, что если ваш класс действительно предназначен для копирования (например, для использования в контейнерах STL), то требуется либо shared_ptr (если вы хотите поделиться impl), либо глубокая копия impl.

Chris Jester-Young 22.11.2008 13:30

Не могли бы вы предоставить более подробную информацию о том, почему это поведение undefined, если у вас нет определяемого пользователем деструктора (может быть, стандартный раздел?)

Johannes Schaub - litb 22.11.2008 14:12

Это явно не указано в Стандарте, но является следствием того, что компилятор должен создать экземпляр встроенного dtor по умолчанию внешнего класса, когда он видит, что dtor не был объявлен. Поскольку существует переменная-член auto_ptr <impl>, dtor по умолчанию должен вызывать auto_ptr <impl> dtor.

fizzer 22.11.2008 14:34

Таким образом, здесь должен быть создан экземпляр ~ auto_ptr <impl>, который будет включать вызов delete impl_;. На этом этапе в блоке трансляции impl_ является неполным, поэтому удаление не определено в 5.3.5 (3). Настоящие компиляторы (например, MSVC++) будут предупреждать об удалении неполного типа здесь.

fizzer 22.11.2008 14:37

Напротив, если вы объявите dtor, компилятор не сгенерирует его по умолчанию. Позже, в вашем файле cpp вы определяете структуру impl вверху, а затем (возможно, пустое) тело вашего dtor. Опять же, создается экземпляр ~ auto_ptr <impl>, но на этот раз impl завершен и удаление определено.

fizzer 22.11.2008 14:41

Я думаю, что это непреднамеренное следствие Стандарта. Если вы выполните поиск на comp.lang.C++. Moderated по запросу «auto_ptr неполный тип», вы найдете лучший анализ, выполненный людьми умнее меня. См. Также страницы boost smart_ptr, на которых IIRC обсуждает проблему и почему некоторые из их указателей не страдают от нее.

fizzer 22.11.2008 14:47

Я только что вспомнил, что в этом случае вы не выбираете между стандартной библиотекой и Boost, вы выбираете между стандартными библиотеками pre-TR1 и TR1. :-)

Chris Jester-Young 22.11.2008 23:23

Проблемы совместимости также затрудняют этот выбор.

Basilevs 10.03.2011 10:58

Альтернативой std::auto_ptr является boost::scoped_ptr. Основное отличие от auto_ptr в том, что boost::scoped_ptr нельзя копировать.

Подробнее см. эта страница.

это не правильно. auto_ptr должен быть подвижным, scoped_ptr - нет. В связанном вами документе также упоминается следующее: «Основная причина использования scoped_ptr, а не auto_ptr заключается в том, чтобы дать читателям вашего кода знать, что вы намереваетесь, что« получение ресурсов - это инициализация », которая будет применяться только для текущей области, и не собираетесь передать право собственности ".

Sebastian Mach 10.08.2011 18:57

Если вы действительно педантичны, нет абсолютной гарантии, что использование члена auto_ptr не требует полного определения параметра шаблона auto_ptr в той точке, в которой он используется. Сказав это, я никогда не видел, чтобы это не работало.

Один из вариантов - использовать const auto_ptr. Это работает до тех пор, пока вы можете создать свой 'pimpl' с новым выражением внутри списка инициализаторов, и гарантирует, что компилятор не могу сгенерирует конструктор копирования по умолчанию и методы присваивания. По-прежнему необходимо предоставить не встроенный деструктор для включающего класса.

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

shared_ptr и scoped_ptr являются в стандартных библиотеках ... TR1. :-)

Chris Jester-Young 22.11.2008 23:22

Верно, но TR1 не является частью стандартов 1998 или 2003, и существует множество сред C++, которые достаточно полно реализуют эти стандарты, но не предоставляют библиотеки TR1. Чем меньше у вас зависимостей; тем более переносимым будет ваш код.

CB Bailey 23.11.2008 03:11

shared_ptr находится в TR1. scoped_ptr - нет.

Rimo 04.06.2010 11:31
Ответ принят как подходящий

Вам не стоит использовать для этого std :: auto_ptr. Деструктор не будет виден в момент объявления std :: auto_ptr, поэтому он может быть вызван неправильно. Предполагается, что вы заранее объявляете свой класс pImpl и создаете экземпляр внутри конструктора в другом файле.

Если вы используете boost :: scoped_ptr (здесь нет необходимости в shared_ptr, вы не будете использовать pimpl совместно с любыми другими объектами, и это обеспечивается тем, что scoped_ptr является не копируемый), вам нужен только деструктор pimpl, видимый в точке, где вы вызываете конструктор scoped_ptr.

Например.

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
};

// Body of these functions in MyClass.cpp

Здесь компилятор сгенерирует деструктор MyClass. Которая должна вызывать деструктор auto_ptr. В момент создания деструктора auto_ptr Pimpl является неполным типом. Таким образом, в деструкторе auto_ptr, когда он удаляет объект Pimpl, он не знает, как вызвать деструктор Pimpl.

boost :: scoped_ptr (и shared_ptr) не имеют этой проблемы, потому что, когда вы вызываете конструктор scoped_ptr (или метод сброса), он также создает эквивалент указателя на функцию, который он будет использовать вместо вызова delete. Ключевым моментом здесь является то, что он создает экземпляр функции освобождения, когда Pimpl не является неполным типом. В качестве побочного примечания, shared_ptr позволяет вам указать функцию настраиваемое освобождение, поэтому вы можете использовать ее для таких вещей, как ручки GDI или что-то еще, что вы можете захотеть, но здесь это излишне для ваших нужд.

Если вы действительно хотите использовать std :: auto_ptr, вам нужно проявить особую осторожность, убедившись, что вы определяете деструктор MyClass в MyClass.cpp, когда Pimpl полностью определен.

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
    ~MyClass();
};

и

// in MyClass.cpp

#include "Pimpl.h"

MyClass::MyClass() : pimpl(new Pimpl(blah))
{
}

MyClass::~MyClass() 
{
    // this needs to be here, even when empty
}

Компилятор сгенерирует код, который эффективно уничтожит все члены MyClass «в» пустом деструкторе. Итак, в момент создания деструктора auto_ptr Pimpl больше не является неполным, и теперь компилятор знает, как вызвать деструктор.

Лично я не думаю, что стоит тратить время на то, чтобы убедиться, что все правильно. Также существует риск, что кто-то придет позже и приведёт в порядок код, удалив кажущийся избыточным деструктор. Так что для такого рода вещей безопаснее использовать boost :: scoped_ptr.

Но вы не можете вызвать pimpl (new Pimpl (blah)), когда используете scoped_ptr, поскольку он не копируется. Так не лучше ли shared_ptr?

Frank 22.03.2009 02:55

Есть ли причина, по которой вы объявляете класс Pimpl вне MyClass? Я спрашиваю, потому что у меня появилась привычка использовать частную структуру для объекта pimpl.

jamuraa 02.12.2009 19:51

этот ответ все еще верен? По крайней мере, в Boost 1.41 похоже, что scoped_ptr требует определения деструктора и конструктора, в котором класс завершен, и вызывает удаление напрямую.

Amnon 22.12.2010 13:06

GCC выдает ошибку об отсутствии деструктора при неправильном использовании auto_ptr для pimpl, поэтому реализация деструктора в любом случае не будет забыта. ИМХО, это сводит проблему к нулю и делает auto_ptr предпочтительнее, чем решения для ускорения.

Basilevs 10.03.2011 10:55

Этот ответ содержит дезинформацию. boost::scoped_ptr использует нет стирание типа для захвата делетера во время конструирования, как это делает shared_ptr. Да и std::unique_ptr в этом отношении не имеет значения. Оба они страдают теми же проблемами, что и std::auto_ptr, в отношении неполных типов. См. scoped_ptr и GOTW 100.

Andrew Durward 03.10.2012 16:55

boost :: shared_ptr специально разработан для работы с идиомой pimpl. Одним из основных преимуществ является то, что он позволяет не определять деструктор для класса, содержащего pimpl. Политика совместного владения может быть как преимуществом, так и недостатком. Но в более позднем случае вы можете правильно определить конструктор копирования.

Даже если он создан для идиомы сутенера, он несет с собой свою природу; вы разделяете impl, что означает, что по умолчанию две копии объекта фактически будут одним и тем же объектом. Такое поведение удивительно, и последнее, что вы хотите сделать, - это удивить пользователей вашего класса.

daramarak 05.05.2010 19:37

Вы можете захотеть, чтобы две копии имели один и тот же объект.

CashCow 09.03.2011 20:08

shared_ptr намного предпочтительнее auto_ptr для pImpl, потому что ваш внешний класс может внезапно потерять свой указатель при его копировании.

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

Можно много сказать об использовании навязчивого счетчика ссылок в pImpl и о том, чтобы внешний класс вызвал его копию и назначил семантику в ее реализации. Предполагая, что это истинная модель поставщика (предоставляет класс), лучше, чтобы поставщик не заставлял пользователя использовать shared_ptr или использовать ту же версию shared_ptr (boost или std).

Если вам нужен копируемый класс, используйте scoped_ptr, который запрещает копирование, что затрудняет неправильное использование вашего класса по умолчанию (по сравнению с использованием shared_ptr, компилятор не будет генерировать средства копирования самостоятельно; а в случае shared_ptr, если вы не знаю, что вы делаете (что достаточно часто бывает даже для мастеров), может возникнуть странное поведение, когда вдруг копия чего-то также модифицирует это что-то), а затем переопределить конструктор копирования и присваивание копии:

class CopyableFoo {
public:
    ...
    CopyableFoo (const CopyableFoo&);
    CopyableFoo& operator= (const CopyableFoo&);
private:
    scoped_ptr<Impl> impl_;
};

...
CopyableFoo (const CopyableFoo& rhs)
    : impl_(new Impl (*rhs.impl_))
{}

вы должны унаследовать от boost :: noncopyable, чтобы сделать ваш класс не копируемым

fmuecke 11.10.2011 18:36

@fmuecke: Но мой CopyableFoo предназначен для копирования. Использование scoped_ptr<> гарантирует, что вы не забудете написать назначение копирования и -конструктор. Поскольку scoped_ptr<> сам по себе не подлежит копированию, компилятор не будет пытаться генерировать их автоматически. Если вы используете shared_ptr<>, компилятор выполнит операции копирования, которые потенциально не будут делать то, что вы намереваетесь. Помните, что мой класс предназначен для копирования, а не для копирования. Конечно, вы правы, что для этого нужно использовать boost::noncopyable.

Sebastian Mach 11.10.2011 19:29

Я был очень доволен impl_ptr Владимира Батова [изменено]. Это упрощает создание pImpl без необходимости явно указывать конструктор копирования и оператор присваивания.

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

Другие вопросы по теме