【问题标题】:Why can't `boost::multi_index replace` be used for pointer types?为什么 `boost::multi_index replace` 不能用于指针类型?
【发布时间】:2014-04-01 05:47:46
【问题描述】:

简答:用户 modify 代替,接受答案中的详细信息以及 this answer

我正在尝试使用持有指针类型的boost::multi_index_container。在我看来,replace 功能已损坏,我想知道我做错了什么。

下面的代码演示了两种情况:第一个容器保存数据的副本(正常工作),第二个容器保存指向数据的指针(失败)。

using namespace boost::multi_index;
using boost::multi_index_container;

struct Data
{
    int key1;
    int key2;
};

using DataContainer =
    multi_index_container<
        Data,
        indexed_by<
            hashed_unique<member<Data, int, &Data::key1>>,
            hashed_unique<member<Data, int, &Data::key2>>>>;

using DataPtrContainer = 
    multi_index_container<
        Data*,
        indexed_by<
            hashed_unique<member<Data, int, &Data::key1>>,
            hashed_unique<member<Data, int, &Data::key2>>>>;

TEST(DummyTest, Test1)
{
    Data data{1,2};
    DataContainer dataContainer;
    dataContainer.insert(data);

    EXPECT_EQ(1, dataContainer.get<0>().find(1)->key1);
    EXPECT_EQ(2, dataContainer.get<0>().find(1)->key2);

    auto iter = dataContainer.get<0>().find(1);
    Data d = *iter;
    d.key2 = 5;
    dataContainer.replace(iter, d);

    EXPECT_EQ(1, dataContainer.get<1>().find(5)->key1);
    EXPECT_EQ(5, dataContainer.get<1>().find(5)->key2);

}

TEST(DummyTest, Test2)
{
    Data* data = new Data{1,2};
    DataPtrContainer dataContainer;
    dataContainer.insert(data);

    EXPECT_EQ(1, (*dataContainer.get<0>().find(1))->key1);
    EXPECT_EQ(2, (*dataContainer.get<0>().find(1))->key2);

    auto iter = dataContainer.get<0>().find(1);
    Data* d = *iter;
    d->key2 = 5;
    dataContainer.replace(iter, d);

    EXPECT_EQ(1, (*dataContainer.get<1>().find(5))->key1);  // fail as the iterator not dereferencable
    EXPECT_EQ(5, (*dataContainer.get<1>().find(5))->key2); // fail as the iterator not dereferencable

}

【问题讨论】:

  • 附言。如果您讨厌测试用例格式并且更喜欢可以复制粘贴编译的代码,请告诉我...
  • 我讨厌测试用例格式,更喜欢可以复制粘贴编译的代码。 :)(虽然说真的:))

标签: c++ boost boost-multi-index


【解决方案1】:

好的,这不是 Boost.MultiIndex 中的错误,而是您的代码中的微妙合同违规。无指针版本:

auto iter = dataContainer.get<0>().find(1);
Data d = *iter;
d.key2 = 5;
dataContainer.replace(iter, d);

正在将所包含值的副本复制到d,对其进行修改,然后将其用于替换:到目前为止一切顺利。但是指针版本一直在打破不变量:

auto iter = dataContainer.get<0>().find(1);
Data* d = *iter;
d->key2 = 5;  // #1: invariant breach here
dataContainer.replace(iter, d); // #2: unexpected behavior ensues

在#1 中,您在未经其同意或不知情的情况下修改了dataContainer 的内部键:一旦您这样做了,被触摸的元素就会被错误地索引。这类似于在无指针版本中抛弃 constness:

auto iter = dataContainer.get<0>().find(1);
const Data& d = *iter;
const_cast<Data&>(d).key2 = 5;

所以,当 #2 被执行时,datacontainer,它不会怀疑你已经改变了它的键,只是验证你提议的替换 d 等同于它已经拥有的,并且什么都不做(然后不重新索引)。

【讨论】:

  • 好的 - 这解释了为什么它不起作用,但从这里看来,你永远不能 replace 指针(没有对象的深层副本)并且使用 modify 是唯一的解决方案?
  • @Zero 是正确的,modify 是在这种情况下要走的路(并且使用 C++11 lambda 函数,生成的代码不会更麻烦)。一个警告说明:replacemodify 在修改失败时表现不同(因为唯一索引中的重复):前者恢复原始值,后者没有以前的值,删除有问题的元素——modify 有一个版本,可以回滚,以防万一这对你很重要。
【解决方案2】:

看来使用modify 函数也可以达到同样的效果(如下所示)。这是一种解决方法,而不是答案,我会接受任何可以使用replace 达到相同结果的答案。

TEST(DummyTest, Test2)
{
    Data* data = new Data{1,2};
    DataPtrContainer dataContainer;
    dataContainer.insert(data);

    EXPECT_EQ(1, (*dataContainer.get<0>().find(1))->key1);
    EXPECT_EQ(2, (*dataContainer.get<0>().find(1))->key2);

    auto iter = dataContainer.get<0>().find(1);
    //Data* d = *iter;
    //d->key2 = 5;
    //dataContainer.replace(iter, d);
    dataContainer.modify(iter, [](Data* data){ data->key2 = 5; });

    EXPECT_EQ(1, (*dataContainer.get<1>().find(5))->key1);
    EXPECT_EQ(5, (*dataContainer.get<1>().find(5))->key2);

}

【讨论】:

  • 赞成,因为这实际上不是一种解决方法,人们应该使用它。
【解决方案3】:

replace () 将与ordered_unique 一起使用。

class FooBar final
{
  public:
    FooBar (uint64_t id, std::string_view name)
      : id_ {id},
        name_ {name}
    {
    }

    uint64_t id () const noexcept { return id_; }

    const std::string & name () const noexcept { return name_; }

    void setName (std::string_view name) { name_ = name; }

  private:
    uint64_t id_;
    std::string name_;
};

    struct FooBarId {};
    struct FooBarName {};

    using FooBars = boost::multi_index::multi_index_container <
        std::shared_ptr <FooBar>,
        boost::multi_index::indexed_by <
            boost::multi_index::hashed_unique <
                boost::multi_index::tag <FooBarId>,
                boost::multi_index::const_mem_fun <
                    FooBar,
                    uint64_t,
                    & FooBar::id
                >
            >,
            boost::multi_index::ordered_unique <
                boost::multi_index::tag <FooBarName>,
                boost::multi_index::const_mem_fun <
                    FooBar,
                    const std::string &,
                    & FooBar::name
                >
            >
        >
    >;

    FooBars fooBars;

    auto fooBar1 = std::make_shared <FooBar> (1, "Test1");
    auto fooBar2 = std::make_shared <FooBar> (2, "Test2");
    auto fooBar3 = std::make_shared <FooBar> (3, "Test3");

    fooBars.emplace (fooBar1);
    fooBars.emplace (fooBar2);
    fooBars.emplace (fooBar3);

    auto it1 = fooBars.get <FooBarName> ().find ("Test2");
    assert (it1 != fooBars.get <FooBarName> ().end ());
    assert ((* it1)->id () == fooBar2->id ());

    fooBar2->setName ("Test4");
    fooBars.get <FooBarName> ().replace (it1, fooBar2);

    assert (fooBars.get <FooBarName> ().find ("Test2") == fooBars.get <FooBarName> ().end ());
    assert (fooBars.get <FooBarName> ().find ("Test4") != fooBars.get <FooBarName> ().end ());

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-09
    相关资源
    最近更新 更多