Путаница в отношении копирования при записи и shared_ptr - PullRequest
8 голосов
/ 05 июня 2011

Я искал в Интернете и прочитал документацию Boost о shared_ptr. Есть ответ на SO, который говорит, что shared_ptr для Copy-On-Write (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 ]

13 голосов
/ 06 июня 2011

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

Большинство советов по 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.

EDIT : после комментария Xeo кажется, что последний элемент (IngredientFactory) довольно лаконичен ...

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

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

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

1 голос
/ 05 июня 2011

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

...