1

I was recently working with some code that uses a std::vector of std::unique_ptr<T> with a custom deleter of type std::function<void(T*)>. The code is attempting to remove values that meet a certain condition from the vector:

size_t j = 0;
for (size_t i = 0; i < vec.size(); i++) {
    if (!condition(vec[i].get()) {
            vec[j] = std::move(out[i]);
            j++;
    }
}
vec.resize(j);

In troubleshooting a bad_function_call thrown from the destructor of vec, I found that when i=j and a unique_ptr is move-constructed with itself, it loses its custom deleter. In other words, vec[j] will still point to the correct value, but its deleter will be a default-constructed std::function.

Why does this happen? What rule does this code break?

FWIW, I can only make it happen with clang, and only on certain platforms. Adding a guard for i=j fixes the problem.

MCVE:

#include <memory>
#include <vector>
#include <functional>

using int_ptr= std::unique_ptr<int, std::function<void(int*)>>;

int_ptr make_int_ptr(int val) {
    auto deleter = [](int* q) { delete q; };
    int* v = new int;
    *v = val;
    return int_ptr{v, deleter};
}

int main() {
    auto a = make_int_ptr(4);
    a = std::move(a);
}
dbaston
  • 903
  • 1
  • 10
  • 20

1 Answers1

3

Standard library classes are not required to check for self-assignment from an rvalue. According to [res.on.arguments]/(1.3),

If a function argument binds to an rvalue reference parameter, the implementation may assume that this parameter is a unique reference to this argument.

Thus, undefined behaviour occurs when you try to perform a self-move-assignment.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312