Case 32 โ Parameter Default Value Changes (C++)¶
| Field | Value |
|---|---|
| Verdict | ๐ API_BREAK |
| Category | API Break |
| Platforms | Linux |
| Flags | API break |
Detected ChangeKinds |
param_default_value_changed, param_default_value_removed |
| Source files | examples/case32_param_defaults/ |
Category: C++ Defaults | Verdict: ๐ API_BREAK (with headers) / NO_CHANGE (object/ELF-only)
A parameter default is removed (
configure(verbose, ...)), which is a source-level break โ callers relying on the default no longer compile. The mangled symbol is unchanged, so the binary ABI is intact. Default-argument values live only in the header, so this is detected only in header (castxml) mode (param_default_value_removed/param_default_value_changed); object/DWARF comparison reports NO_CHANGE.
What changes¶
| Method | v1 signature | v2 signature | Effect |
|---|---|---|---|
connect |
void connect(int timeout = 30) |
void connect(int timeout = 60) |
Default changed |
configure |
void configure(bool verbose = true, int retries = 3) |
void configure(bool verbose, int retries = 5) |
verbose lost default; retries changed |
disconnect |
void disconnect(int code) |
void disconnect(int code = 0) |
Default added |
Why this is NOT a binary ABI break¶
In C++, default parameter values are resolved at the call site during compilation.
When the compiler sees conn.connect(), it rewrites it to conn.connect(30) using
the default from the header at compile time. The library's .so never knows about
default values โ it only receives the actual arguments.
This means:
-
Default changed (timeout 30 -> 60): Binaries compiled against v1 will always pass
30. Only code recompiled against v2 will pass60. No binary break. -
Default removed (verbose): Binaries compiled against v1 already have
truebaked in. Only recompilation against v2 would fail (source break) becauseconfigure()with zero args is no longer valid. -
Default added (disconnect): Existing binaries already pass explicit values. New code compiled against v2 can call
disconnect()without arguments. Fully backward compatible.
The mangled symbol names are identical (_ZN10Connection7connectEi, etc.) because
default values do not participate in name mangling.
Code diff¶
class Connection {
public:
- void connect(int timeout = 30);
- void configure(bool verbose = true, int retries = 3);
- void disconnect(int code);
+ void connect(int timeout = 60);
+ void configure(bool verbose, int retries = 5);
+ void disconnect(int code = 0);
};
Real Failure Demo¶
Severity: NONE (binary compatible)
Scenario: Compile app against v1 headers, swap in v2 .so.
# Build v1 library + app
g++ -shared -fPIC -g v1.cpp -o libfoo.so
g++ -g app.cpp -I. -L. -lfoo -Wl,-rpath,. -o app
./app
# โ Parameter defaults demo (compiled against v1.hpp):
# โ
# โ Calling connect() with default timeout:
# โ Compiled as connect(30) from v1 header
# โ OK โ v2 default is 60, but caller already passed 30
# โ
# โ Calling connect(45) with explicit timeout:
# โ OK โ explicit args are unaffected
# โ
# โ Calling configure() with defaults:
# โ Compiled as configure(true, 3) from v1 header
# โ OK โ v2 removed verbose default, but caller already passed true
# โ ...
# Swap in v2 (no recompile)
g++ -shared -fPIC -g v2.cpp -o libfoo.so
./app
# โ Output is IDENTICAL โ defaults were baked into the caller at compile time.
# โ The v2 library receives the same arguments as v1.
Source break verification (partial โ configure() with no args fails):
# Create a minimal source that includes only v2.hpp and calls configure()
cat > /tmp/source_break.cpp << 'SRC'
#include "v2.hpp"
int main() {
Connection conn;
conn.configure(); // no args โ v2 requires explicit 'verbose'
return 0;
}
SRC
g++ -g /tmp/source_break.cpp -I. -L. -lfoo -Wl,-rpath,. -o /tmp/app_v2 2>&1
# โ error: no matching function for call to 'Connection::configure()'
# โ note: candidate expects 2 arguments, 0 provided
# (because 'verbose' lost its default in v2)
rm -f /tmp/source_break.cpp /tmp/app_v2
Reproduce with abicheck¶
g++ -shared -fPIC -g v1.cpp -o libfoo_v1.so
g++ -shared -fPIC -g v2.cpp -o libfoo_v2.so
abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml
echo "exit: $?" # โ 0 (no binary ABI change)
How to fix¶
No fix needed for binary compatibility. For source compatibility:
- Do not remove defaults from public headers in minor releases.
- If a default value must change, document it clearly โ existing compiled binaries will silently continue using the old default until recompiled.
- Consider using overloaded functions instead of defaults for critical parameters where behavioral differences matter.
References¶
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hppv2.cppv2.hpp
See also: Examples overview ยท All API_BREAK cases ยท Category: API Break.