Путаница в отношении копирования при записи и shared_ptr

Я искал в Интернете и прочитал документацию Boost о shared_ptr, Есть ответ на SO, который говорит, что shared_ptr для копирования при записи (COW) отстой и что TR! удалил его из строковых библиотек. Большинство советов по SO говорит, чтобы использовать shared_ptr а не обычные указатели.

В документации также говорится об использовании std::unique() сделать указатель COW, но я не нашел никаких примеров.

Речь идет о наличии умного указателя, который выполняет COW для вас или о том, чтобы ваш объект использовал новый shared_ptr клонированному объекту, а затем изменить клонированный объект?

Пример: рецепты и ингредиенты

struct Nutrients;

struct Ingredient
{
    Ingredient(const std::string& new_title = std::string(""))
        : m_title(new_title)
        { ; }
    std::string m_title;
    Nutrients   ing_nutrients;
};

struct Milk : public Ingredient
    : Ingredient("milk")
{ ; }

struct Cream : public Ingredient
    : Ingredient("cream")
{ ; }

struct Recipe
{
    std::vector< boost::shared_ptr<Ingredient> > m_ingredients;
    void append_ingredient(boost::shared_ptr<Ingredient> new_ingredient)
    {
        m_ingredients.push_back(new_ingredient);
        return;
    }
    void replace_ingredient(const std::string& original_ingredient_title,
                            boost::shared_ptr<Ingredient> new_ingredient)
    {
        // Confusion here
    }
};

int main(void)
{
    // Create an oatmeal recipe that contains milk.
    Recipe  oatmeal;
    boost::shared_ptr<Ingredient> p_milk(new Milk);
    oatmeal.add_ingredient(p_milk);

    // Create a mashed potatoes recipe that contains milk
    Recipe  mashed_potatoes;
    mashed_potatoes.add_ingredient(p_milk);

    // Now replace the Milk in the oatmeal with cream
    // This must not affect the mashed_potatoes recipe.
    boost::shared_ptr<Ingredient> p_cream(new Cream);
    oatmeal.replace(p_milk->m_title, p_cream);

    return 0;
}

Путаница в том, как заменить "молоко" в oatmeal рецепт с кремом и не влияет mashed_potatoes рецепт.

Мой алгоритм:

locate pointer to `Milk` ingredient in the vector.
erase it.
append `Cream` ingredient to vector.

Как бы указатель COW вступил в игру здесь?

Примечание: я использую MS Visual Studio 2010 на Windows NT, Vista и 7.

2 ответа

Решение

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

В большинстве советов по SO сказано использовать shared_ptr, а не обычные указатели.

Да и нет. Ряд пользователей SO, к сожалению, рекомендуют shared_ptr как будто это была серебряная пуля, чтобы решить все проблемы, связанные с управлением памятью. Это не так. Большинство советов говорят о том, чтобы не использовать голые указатели, что существенно отличается.

Настоящим советом является использование умных менеджеров: будь то умные указатели (unique_ptr, scoped_ptr, shared_ptr, auto_ptr), умные контейнеры (ptr_vector, ptr_map) или нестандартные решения для сложных задач (на основе Boost.MultiIndex, с использованием навязчивых счетчиков и т. д.).

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

Что такое COW?

COW (Copy-On-Write) - это обмен данными для "экономии" памяти и удешевления копирования... без изменения семантики программы.

С точки зрения пользователя, будь то std::string использовать COW или нет, не имеет значения. Когда строка модифицируется, все остальные строки не затрагиваются.

Идея, лежащая в основе COW, заключается в том, что:

  • если вы являетесь единственным владельцем данных, вы можете изменить их
  • если это не так, то вы должны скопировать его, а затем использовать копию вместо

Похоже на shared_ptr, так почему не?

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

Проблема в том, что с shared_ptr предназначен для бесперебойной работы независимо от того, является ли право собственности общим, для COW сложно реализовать тест "если единственный владелец". Примечательно, что взаимодействие weak_ptr затрудняет

Это возможно, очевидно. Ключ не должен пропускать shared_ptrи вовсе не использовать weak_ptr (они бесполезны для COW в любом случае).

Это имеет значение?

Нет, не совсем. Доказано, что COW не так уж и хорош. В большинстве случаев это микрооптимизация... и микропессимизация одновременно. Вы можете сэкономить некоторую память (хотя это работает, только если вы не копируете большие объекты), но вы усложняете алгоритм, что может замедлить выполнение (вы вводите тесты).

Мой совет будет не использовать COW. И не использовать те shared_ptr или.


Лично я бы либо:

  • использование boost::ptr_vector<Ingredient> скорее, чем std::vector< boost::shared_ptr<Ingredient> > (вам не нужно делиться)
  • создать IngredientFactoryчто бы создать (и управлять) ингредиенты, и вернуть Ingredient const&, Factory должен пережить любой Receipt,

РЕДАКТИРОВАТЬ: после комментария Xeo, кажется, последний пункт (IngredientFactory) довольно лаконичен...

В случае с IngredientFactory, Receipt объект будет содержать std::vector<Ingredient const*>, Обратите внимание на необработанный указатель:

  • Receipt не несет ответственности за память, но получает доступ к ней
  • Существует неявная гарантия того, что указанный объект будет оставаться в силе дольше, чем Receipt объект

Можно использовать сырые (обнаженные) указатели, если вы относитесь к ним так, как если бы вы указывали. Вам просто нужно остерегаться потенциальной аннулирования, и вам предлагается возможность переустановить их, если вы того пожелаете, - и вы доверяете провайдеру заботиться о аспектах управления временем жизни / памятью.

Вам не о чем беспокоиться. каждый Recipe объект имеет свой vectorпоэтому изменение одного не повлияет на другое, даже если оба они содержат указатели на одни и те же объекты. Рецепт картофельного пюре будет затронут, только если вы измените содержимое объекта, который p_milk указывает на, но вы этого не делаете. Вы модифицируете oatmeal.m_ingredients объект, который не имеет абсолютно никакого отношения к mashed_potatoes.m_ingredients, Они два совершенно независимых vector экземпляров.

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