refactor: remove unnecessary std::move for trivially copyable types#34514
refactor: remove unnecessary std::move for trivially copyable types#34514l0rinc wants to merge 4 commits intobitcoin:masterfrom
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. LLM Linter (✨ experimental)Possible places where named args for integral literals may be used (e.g.
2026-02-19 23:12:09 |
move-const-arg for trivially-copyable types
maflcko
left a comment
There was a problem hiding this comment.
Not sure, but I don't mind the change.
Just to clarify, this was done intentionally, see the reasons inline. Maybe docs or a comment could be added to clarify this?
src/txmempool.cpp
Outdated
| CTxMemPool::CTxMemPool(Options opts, bilingual_str& error) | ||
| : m_opts{Flatten(std::move(opts), error)} | ||
| CTxMemPool::CTxMemPool(const Options& opts, bilingual_str& error) | ||
| : m_opts{Flatten(opts, error)} |
There was a problem hiding this comment.
This was written intentionally, because:
- If the options layout changes in the future, it will optimize "for free"
- There is no harm in having the move, other than it being a bit confusing
- Using
std::movealso has the benefit to mark a symbol used, regardless of its memory layout (ref use-after-move clang-tidy check)
There was a problem hiding this comment.
I'm not sure about the third point.
the benefit to mark a symbol as used
Why is it a benefit in this case? Wouldn't this be better leaving the constructor parameter alone and passing a non-const ref to Flatten?
There was a problem hiding this comment.
right. Maybe here, but not in src/random.cpp. Re-using a cleared vanilla hasher after move for randomness seems slightly non-ideal.
There was a problem hiding this comment.
Re-using a cleared vanilla hasher after move for randomness seems slightly non-ideal
I also found that confusing, reverted it here.
If the options layout changes in the future, it will optimize "for free"
I don't agree with this, but I have reverted it, we can discuss it separately.
There was a problem hiding this comment.
nit: It is unspecified whether std::source_location is trivial or not: https://en.cppreference.com/w/cpp/utility/source_location/source_location.html
There was a problem hiding this comment.
Thanks, added a static_assert in logging_LogPrintStr for SourceLocation triviality to document why we're not using std::move for these types
| return nSubsidy; | ||
| } | ||
|
|
||
| CoinsViews::CoinsViews(DBParams db_params, CoinsViewOptions options) |
There was a problem hiding this comment.
nit: Might as well dodge one copy, even though the type is barely bigger than a pointer:
| CoinsViews::CoinsViews(DBParams db_params, CoinsViewOptions options) | |
| CoinsViews::CoinsViews(DBParams db_params, const CoinsViewOptions& options) |
There was a problem hiding this comment.
In constructors, the pass-by-value-and-move-into-place is the right approach. This only applies to constructors, not setter functions. See https://stackoverflow.com/questions/26261007/why-is-value-taking-setter-member-functions-not-recommended-in-herb-sutters-cpp for details.
There was a problem hiding this comment.
That makes sense for types like std::string which likely contain pointers to memory in other locations (also true for DBParams in this example). It's not as clear-cut for simpler types like CoinsViewOptions:
Lines 26 to 31 in 32f00a5
Taking by const-ref should be slightly more optimal, but it's not important to me. Maybe the 2 fields of the struct can be passed in 2 registers, I'm not too familiar with calling conventions.
There was a problem hiding this comment.
Reverted all Option moves
|
Using Using I would consider setting |
I think all of your points are already implemented since faad673 (zirka 2022) |
|
I'm ~0 on this:
|
|
Fixed the docs in #34523 |
32f00a5 to
6c7b943
Compare
|
Thanks for the comments, while I think signaling that a value should not be reused after a call via |
move-const-arg for trivially-copyable types|
lgtm, but there are a few conflicts let's get those in earlier. Feel free to ping me for review after a week of inactivity or so, but i don't think there is need to ping reviewers on the second day of a refactor pull request. |
6c7b943 to
c7782b7
Compare
|
Why not introduce function |
…llyCopyableMove=false fa88ac3 doc: Clarify why performance-move-const-arg.CheckTriviallyCopyableMove=false (MarcoFalke) Pull request description: Without this doc, there is a risk that the setting will be turned off, see #34514. The reason to disable it is to catch logic bugs, even on trivially copyable types: ```cpp #include <utility> void Eat(int&& food) { food = 0; }; int main() { int food{2}; Eat(std::move(food)); Eat(std::move(food)); // This should err } ``` ACKs for top commit: l0rinc: ACK fa88ac3 hebasto: ACK fa88ac3. sedited: ACK fa88ac3 Tree-SHA512: d1bda846a10190a2936084a06bd87418c6a3e4ababc298e4beb9bc9e1190bff430cbe973475d634eda5ef7863571c89bfa4b78ff63fcbd9ac10c42fd9d5fa23a
c7782b7 to
1414bfe
Compare
Rebased after #34523, the conflict list only contains my own change now.
You mean like the https://en.cppreference.com/w/cpp/utility/move_if_noexcept.html, but for the trivial types that we still want to avoid reusing? I'm not sure about that, but maybe if you could give a concrete diff it would help. |
Yep, exactly like that |
|
I think it would be easier to enforce this with clang-tidy via:
instead of However, the storage of ipv6 in |
running it locally and grepping for |
Yeah, right. Looks a bit more verbose: a diffdiff --git a/src/netaddress.cpp b/src/netaddress.cpp
index fb2c254076..201c228283 100644
--- a/src/netaddress.cpp
+++ b/src/netaddress.cpp
@@ -26,6 +26,8 @@ using util::HasPrefix;
CNetAddr::BIP155Network CNetAddr::GetBIP155Network() const
{
+ static_assert(std::is_trivially_copyable_v<CNetAddr>);
+ static_assert(std::is_trivially_copyable_v<decltype(CNetAddr::m_addr)>);
switch (m_net) {
case NET_IPV4:
return BIP155Network::IPV4;
diff --git a/src/netaddress.h b/src/netaddress.h
index 2191da54b7..86463879b1 100644
--- a/src/netaddress.h
+++ b/src/netaddress.h
@@ -106,6 +106,93 @@ static constexpr uint16_t I2P_SAM31_PORT{0};
std::string OnionToString(std::span<const uint8_t> addr);
+#include <algorithm>
+#include <cstddef>
+#include <stdexcept>
+
+template <std::size_t N, BasicByte B>
+class ByteArrayVec
+{
+private:
+ B data_[N]{};
+ std::size_t size_{0};
+
+public:
+ constexpr ByteArrayVec() = default;
+
+ constexpr void push_back(B b)
+ {
+ if (size_ >= N) [[unlikely]] {
+ throw std::out_of_range("ByteArrayVec capacity exceeded");
+ }
+ data_[size_++] = b;
+ }
+
+ constexpr void pop_back()
+ {
+ if (size_ == 0) [[unlikely]] {
+ throw std::out_of_range("ByteArrayVec underflow: pop_back called on empty container");
+ }
+ --size_;
+ }
+
+ constexpr void resize(std::size_t new_size)
+ {
+ if (new_size > N) [[unlikely]] {
+ throw std::out_of_range("ByteArrayVec resize exceeds capacity");
+ }
+ if (new_size > size_) {
+ // Zero-initialize the appended bytes
+ std::fill_n(data_ + size_, new_size - size_, B{0});
+ }
+ size_ = new_size;
+ }
+
+ constexpr void assign(std::size_t count, B value)
+ {
+ if (count > N) [[unlikely]] {
+ throw std::out_of_range("ByteArrayVec assign exceeds capacity");
+ }
+ std::fill_n(data_, count, value);
+ size_ = count;
+ }
+
+ template <std::input_iterator It>
+ constexpr void assign(It first, It last)
+ {
+ const auto new_size = static_cast<std::size_t>(std::distance(first, last));
+ if (new_size > N) [[unlikely]] {
+ throw std::out_of_range("ByteArrayVec assign range exceeds capacity");
+ }
+ std::copy(first, last, data_);
+ size_ = new_size;
+ }
+
+ constexpr void clear() noexcept { size_ = 0; }
+
+ constexpr B& operator[](std::size_t i) { return data_[i]; }
+ constexpr B operator[](std::size_t i) const { return data_[i]; }
+
+ constexpr B* data() noexcept { return data_; }
+ constexpr const B* data() const noexcept { return data_; }
+
+ constexpr std::size_t size() const noexcept { return size_; }
+ constexpr bool empty() const noexcept { return size_ == 0; }
+ constexpr std::size_t capacity() const noexcept { return N; }
+
+ constexpr B* begin() noexcept { return data_; }
+ constexpr B* end() noexcept { return data_ + size_; }
+ constexpr const B* begin() const noexcept { return data_; }
+ constexpr const B* end() const noexcept { return data_ + size_; }
+
+ constexpr bool operator==(const ByteArrayVec& other) const noexcept { return std::equal(begin(), end(), other.begin(), other.end()); }
+ constexpr std::strong_ordering operator<=>(const ByteArrayVec& other) const noexcept
+ {
+ return std::lexicographical_compare_three_way(begin(), end(), other.begin(), other.end());
+ }
+};
+
+
/**
* Network address.
*/
@@ -116,7 +203,7 @@ protected:
* Raw representation of the network address.
* In network byte order (big endian) for IPv4 and IPv6.
*/
- prevector<ADDR_IPV6_SIZE, uint8_t> m_addr{ADDR_IPV6_SIZE, 0x0};
+ ByteArrayVec<ADDR_IPV6_SIZE, uint8_t> m_addr{};
/**
* Network to which this address belongs. |
1414bfe to
5152fdb
Compare
`EmplaceCoinInternalDANGER` took `COutPoint&&`, forcing callers to `std::move()` a trivially-copyable value. Take the `outpoint` by const reference instead. Co-authored-by: Andrew Toth <andrewstoth@gmail.com>
Logging helpers took `SourceLocation&&`, forcing pointless `std::move()` at call sites even though `SourceLocation` is trivially copyable. Added static assert to related test to document this being the case. Accept `const SourceLocation&` in `LogPrintFormatInternal`, `LogPrintStr`, and `LogPrintStr_`.
… in constructors
5152fdb to
597fd6e
Compare
Inspired by #34320 (comment).
Problem
A few function signatures in the codebase use rvalue references for trivially copyable types, forcing callers to
std::move()values where there is no benefit - for trivially copyable types, moving is semantically identical to copying (see https://godbolt.org/z/hdrn17v9b).Additionally, some call sites use
std::move()on plain enums and primitive types where it is basically just noise.Note:
CheckTriviallyCopyableMoveremainsfalse-std::move()on trivially copyable types is still permitted where it serves as intent documentation (e.g. signaling that a value should not be reused after a call).Fix
CheckTriviallyCopyableMoveis kept disabled (to preservebugprone-use-after-movecoverage on trivially copyable types wherestd::movesignals intent), cherry-picked from doc: Clarify why performance-move-const-arg.CheckTriviallyCopyableMove=false #34523EmplaceCoinInternalDANGERto takeconst COutPoint&instead ofCOutPoint&&const SourceLocation&instead ofSourceLocation&&std::move()on enum types in RPC constructors and onbool/intmembers intxgraph.cpp