1

I have a situation similar to the one described in this thread: Getting around Boost Multi-Index container's constant elemets. In short, I have a boost multi index container holding a struct like this:

struct Data {
    Controller controller;
    const int indexed_data;
}

My indexed data never changes, but the controller does. So, my first approach was to do all I need in a modify call:

container.modify(it, [](auto& data) {
    data.controller.nonConstFunction();
});

Apparently this works for me, but I was performing some tests to understand the behaviour of this method when an exception is thrown inside the lambda (which will happen in my case), and I was surprised with the result:

struct employee {
    int id;
    std::string name;
}

typedef multi_index_container<
    employee,
    indexed_by<
        ordered_unique<BOOST_MULTI_INDEX_MEMBER(employee,int,id)>>
    >
> employee_set;

employee_set es;    
es.insert(employee{0,"Joe"});
es.insert(employee{1,"Carl"});
es.insert(employee{2,"Robert"});
es.insert(employee{4,"John"});

try {
    auto it = es.find(4); // John
    es.modify(it, [](auto& employee) {
        employee.id = 1; // Same ID of Carl, should be erased
        throw std::runtime_error("test");
    });
} catch (const std::runtime_error& err) {
    // handle error...
}

After this, if you print the contents of the container you get:

0 Joe
1 Carl
2 Robert
1 John

Although without throwing the exception and only changing the employee's ID to one already existent, the hit on the index is detected and the employee being modified is erased.

Like I said, I don't perform any changes to my container's keys, but after finding out about this I am concerned. Is this a bug on the library? Are there any other situations that can lead to an invalid state of the indexes? I also couldn't find a way to "reprocess" the indexes to a valid state besides manually deleting/modifying one of the "invalid" entries.

Apart from that (and going back to my own case), is using modify the best approach to actually call methods on my controller, or should I follow the advise of the thread beforementioned and declare my controller mutable? The latter looks quite dangerous for me because one could easily mess around with the index by declaring anything mutable, but as I just showed, the safe way of doing it has just proved to be not so safe after all.

1 Answers1

2

It's remarkable that you bring this issue up, because the behavior of modify has remained stable for 12+ years until Jon Kalb pointed my attention to a closely related problem in 2016.

In short, modify expects the user-provided modifier not to throw unless it doesn't change the keys of the elements, in which case undefined behavior ensues, as you have discovered. Documentation says so (italics mine):

Exception safety: Basic. If an exception is thrown by some user-provided operation (except possibly mod), then the element pointed to by position is erased.

Whether this is a bug or a design flaw is debatable, but in any case I recently changed the implementation so that your use case will now result in the element being erased. This updated behavior will be available in the upcoming Boost 1.66 release (Dec 2017), when the docs will read:

Exception safety: Basic. If an exception is thrown by some user-provided operation (including mod), then the element pointed to by position is erased.

In the meantime, you need to protect your modifier from after-modification throws --sorry about that. If you're in desperate need of the new modify behavior you can download Boost.MultiIndex source code and patch your local Boost installation.

Joaquín M López Muñoz
  • 5,243
  • 1
  • 15
  • 20
  • Thank you! I confess I had some trouble understanding the excerpt you highlighted, although it indeed looked very much to me it was related to my problem. Anyway, assuming I won't modify any keys when executing `mod`, would you rather keep using `modify` or just declare the controller `mutable` as you suggested yourself on the other thread I mentioned? – Frederico Pantuzza Oct 22 '17 at 15:45
  • 2
    Well, there is a trade off here between safety and performance: `modify` is an expensive operation involving checking consistency (i.e. making sure no keys have been modified) while making things `mutable` has zero overhead. I'd go for `modify` until your profiling indicates more performance is needed. – Joaquín M López Muñoz Oct 22 '17 at 17:17