TGViewer
C++: Хроники Дурки🚑 C++: Хроники Дурки🚑 @cpp_durka · 916 subscribers
Post #27 710
Разберем вот такой вот пример. Что в нем не так?

template<typename U>
struct Scheduler {

// ...

void Add(U&& callback) {
callback_ = std::move(callback);
}

// ...
U callback_;
};


И это какая-то иллюстрация Эффекта Манделлы, потому что часто на вопрос "что не так?" слышу простое, легкое для понимания, неправильное решение в духе

forwarding reference passed to std::move(), which may unexpectedly cause lvalues to be moved; use std::forward() instead


На самом деле, использование std::forward тут нежелательно.

Давайте разберем сразу 4 примера:

// 1
template<typename U>
struct Scheduler {
// ...

void Add(U&& callback) {
callback_ = std::move(callback);
}

// ...
U callback_;
};


// 2
template<typename U>
struct Scheduler {
// ...

void Add(U&& callback) {
callback_ = std::forward<U>(callback);
}

// ...
U callback_;
};


// 3
struct AnotherScheduler {
// ...

template<typename U>
void Add(U&& callback) {
callback_ = std::move(callback);
}

// ...
std::function<void()> callback_;
};


// 4
struct AnotherScheduler {
// ...

template<typename U>
void Add(U&& callback) {
callback_ = std::forward<U>(callback);
}

// ...
std::function<void()> callback_;
};


В первом примере все хорошо: тип зафиксирован в момент объявления инстанса класса
    Scheduler<std::function<void()>> sh;


поэтому в объявлении функции U&& -> это rvalue reference. И поскольку rvalue reference is lvalue, мы должны его мувать. Если использовать std::forward (Как в примере номер 2), то ничего страшного не случиться, но будет выглядеть странно, и заставит ругаться clang-tidy. Возможно, будут какие-то гадости, которые я не смог воспроизвести.


В примерах 3-4 все наоборот. Тип U определяется на этапе вызова функции, и его мувать опасно: у нас аргумент функции - forwarding reference, а значит там может быть как rvalue, так и просто ссылка. Если вы муваете ссылку внутри функции, тот, кто ее вызывает, может внезапно обнаружить, что переданный объект, который он не мувал, изчез:

    AnotherScheduler ash;
auto af = []() -> void {};
ash.Add(std::ref(af)); // moved here inside function
af(); // UB


А потому пример 3 - неверный и потенциальный источник багов. А пример 4 - норм.

Итого, "корректные" решения - 1 и 4. И, как и положено в С++, примеры 2 и 3 нормально скомпилируются... И даже будут как-то работать, наверное, в большинстве случаев, но делать так не надо.


И вообще, читаем cpp core guidelines.
  • 👍 16
  • 🔥 3
More from @cpp_durka
  1. Sep 8, 2026Мошенники заставили пенсионерку из Москвы переписать квартиру на Rust
  2. Aug 31, 2026Всяко разное болезненное есть в С++, из всего болезненного одно из самых болезненных - это…
  3. Aug 24, 2026Если кто не знаком с библиотекой nlohmann/json - она прекрасна. Мои мысли о том, как должн…
  4. Aug 21, 2026#толькосвоимемы Код взят тут. if (dim == 0) idx_dim = i0; else if (dim == 1) idx_dim = i1;…
  5. Aug 17, 2026Ладно, искать баги в компиляторах весело, но недостаточно. Давайте поиграемся в чуть более…
  6. Aug 10, 2026Ладно, разумеется прошлый пост был набросом. Никогда не обновляйте компиляторы без очень с…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →