关于Copy-On-Write和shared_ptr的困惑

时间:2011-06-05 19:16:33

标签: c++ boost shared-ptr copy-on-write

我搜索过网络并阅读有关shared_ptr的Boost文档。 SO上有一个响应,表示写入时写入(COW)的shared_ptr很糟糕,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指针如何在这里发挥作用?

注意:我在Windows NT,Vista和7上使用MS Visual Studio 2010。

2 个答案:

答案 0 :(得分:13)

这里有几个问题捆绑在一起,所以如果我没有按照你期望的顺序解决这些问题,请耐心等待。

  

关于SO的大多数建议都说使用shared_ptr而不是常规指针。

是和否。不幸的是,许多SO的用户推荐shared_ptr,好像它是解决所有内存管理相关问题的灵丹妙药。 不是。大多数建议谈论不使用裸指针,这是完全不同的。

真正的建议是使用智能管理器:智能指针(unique_ptrscoped_ptrshared_ptrauto_ptr),智能容器(ptr_vectorptr_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)非常简洁......

对于IngredientFactoryReceipt对象将包含std::vector<Ingredient const*>。注意原始指针:

  • Receipt不对内存负责,但可以访问它
  • 隐含保证指向的对象将保持有效的时间长于Receipt对象

使用原始(裸)指针是好的,只要你像对待它一样对待它们。您只需要注意潜在的无效性,并且如果您愿意,您可以重新安装它们 - 并且您相信提供商可以处理生命周期/内存管理方面的问题。

答案 1 :(得分:1)

你无需担心。每个Recipe对象都有自己的vector,因此修改它们不会影响另一个,即使它们都碰巧包含指向同一对象的指针。只有当您更改了p_milk指向的对象的内容时,土豆泥配方才会受到影响,但您没有这样做。您正在修改oatmeal.m_ingredients对象,该对象与mashed_potatoes.m_ingredients完全无关。他们是两个完全独立的vector个实例。