8

我已经在网上搜索并阅读了有关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.

牛指针如何在这里发挥作用?

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

4

2 回答 2

15

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

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

是的,也不是。不幸的是,许多 SO 用户建议shared_ptr将其视为解决所有内存管理相关问题的灵丹妙药。它不是。大多数建议都在谈论不使用裸指针,这是完全不同的。

真正的建议是使用智能管理器:无论是智能指针 ( unique_ptr, scoped_ptr, shared_ptr, auto_ptr)、智能容器 ( ptr_vector, ptr_map) 还是解决难题的自定义解决方案(基于 Boost.MultiIndex,使用侵入式计数器等)。

您应该根据需要选择要使用的智能管理器。最值得注意的是,如果您不需要共享对象的所有权,那么您不应该使用shared_ptr.

什么是牛?

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> >(您不需要共享)
  • 创建 a IngredientFactory,它将创建(和管理)成分,并返回 a Ingredient const&Factory应该比任何Receipt.

编辑:在 Xeo 的评论之后,最后一项()似乎IngredientFactory很简洁......

在 的情况下IngredientFactoryReceipt对象将包含一个std::vector<Ingredient const*>。注意原始指针:

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

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

于 2011-06-06T08:13:40.727 回答
1

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

于 2011-06-05T19:28:40.863 回答