Passing Structures
Design Guidelines
When designing structures for parameter passing, consider efficiency and maintainability:
1) Prefer pass-by-value for small, trivially copyable structs (roughly a few machine words). This threshold is ABI- and platform-dependent—measure if performance matters.
2) For larger or non-trivially copyable structs, pass by `const&` for read-only access, or use pass-by-value-then-move when the function intends to consume/retain a copy.
3) Apply the Rule of Three/Five/Zero: either rely on generated special members (Rule of Zero), or explicitly define copy/move/dtor as needed (Three/Five).
4) Consider logical immutability (e.g., `const` data members or non-mutating interfaces) for safer, more predictable behavior.
Passing Strategies (Examples)
Pick parameter style based on how the callee uses the data:
#include <string>
#include <utility>
struct Config {
std::string name;
int timeout_ms;
bool logging;
};
// Read-only: pass by const reference
void print_config(const Config& cfg);
// Consume/retain: pass by value, then move into storage
void set_global_config(Config cfg) {
// global_cfg = std::move(cfg);
}
// Mutate caller's object: pass by non-const reference
void enable_logging(Config& cfg) {
cfg.logging = true;
}
// Sometimes: overload for rvalues if heavy to copy
void set_name(Config& cfg, const std::string& s) { cfg.name = s; }
void set_name(Config& cfg, std::string&& s) { cfg.name = std::move(s); }
Structured Bindings
C++17 structured bindings let you decompose aggregates, tuples, and array-like types into individual variables.
struct Point { int x; int y; };
Point getCenter();
int main() {
auto [x, y] = getCenter(); // copies members from the returned Point
Point p{10, 20};
auto& [rx, ry] = p; // binds by reference; modifies 'p' when 'rx/ry' change
rx = 42; // p.x becomes 42
}
Designated Initializers (C++20)
C++20 designated initializers make aggregate initialization clearer. Designators must appear in the member declaration order and cannot be reordered.
#include <string>
struct Config {
std::string name;
int timeout_ms;
bool logging;
};
Config c {
.name = "app",
.timeout_ms = 1000,
.logging = true
}; // OK: matches declaration order
// Config bad { .logging = true, .name = "app", .timeout_ms = 1000 }; // Ill-formed: reorders members
Avoiding Copies with Moves
If a function takes ownership or needs its own copy, pass the struct by value and move from the argument internally. Callers can pass rvalues or `std::move` lvalues to avoid extra copies.
#include <string>
#include <utility>
struct Payload { std::string data; };
void consume(Payload p) {
// store = std::move(p); // move into a persistent location
}
void demo() {
Payload a{"hello"};
consume(a); // may copy
consume(std::move(a)); // moves from 'a'
consume(Payload{"x"}); // moves (temporary)
}
Pitfalls & Notes
• Passing derived objects by value slices them to the base subobject—use references/pointers for polymorphism.
• Large structs with owning members (e.g., vectors, strings) are cheap to move but potentially expensive to copy—choose parameter style accordingly.
• Prefer `std::span`/views over copying containers when you only need to read elements.