求示例说明:小型自定义类型传值并非最佳实践
Great question! Scott Meyers' guidance in Effective C++ is super important here—don’t mistake "small memory size" for "cheap to copy" when dealing with user-defined types. Let’s walk through a concrete example to see exactly why pass-by-value can be surprisingly costly, even for tiny custom objects.
First, let’s clarify the core difference: built-in types like int or char are trivial to copy—their copy is just a raw bitwise memory copy with zero hidden overhead. But user-defined types, no matter how small their footprint, can have hidden copy costs that add up quickly. Even if your object is only a few bytes, copying it might trigger constructor/destructor calls, deep copies of internal data, or other operations that aren’t obvious at first glance.
Example: A "Small" Type with Expensive Copy Semantics
Let’s create a simple custom type that looks tiny on the surface, but has expensive copy behavior:
#include <iostream> #include <string> // This type is only the size of a pointer (sizeof(SmallType) == sizeof(void*)) class SmallType { private: std::string* internal_string; public: // Constructor: Allocates a new string on the heap SmallType(const std::string& str) : internal_string(new std::string(str)) { std::cout << "✅ Constructing SmallType\n"; } // Copy Constructor: Performs a deep copy of the internal string SmallType(const SmallType& other) : internal_string(new std::string(*other.internal_string)) { std::cout << "⚠️ COPYING SmallType (deep copy in progress!)\n"; } // Destructor: Cleans up the heap-allocated string ~SmallType() { delete internal_string; std::cout << "❌ Destroying SmallType\n"; } // Getter for the internal string const std::string& get_content() const { return *internal_string; } }; // Function that takes SmallType by VALUE void handle_by_value(SmallType val) { std::cout << "Processing value: " << val.get_content() << "\n"; } // Function that takes SmallType by CONST REFERENCE void handle_by_ref(const SmallType& val) { std::cout << "Processing reference: " << val.get_content() << "\n"; } int main() { SmallType my_obj("Hello, Stack Overflow!"); std::cout << "\n--- Calling handle_by_value ---\n"; handle_by_value(my_obj); std::cout << "\n--- Calling handle_by_ref ---\n"; handle_by_ref(my_obj); return 0; }
What Happens When We Run This?
Here’s the output you’ll see:
✅ Constructing SmallType --- Calling handle_by_value --- ⚠️ COPYING SmallType (deep copy in progress!) Processing value: Hello, Stack Overflow! ❌ Destroying SmallType --- Calling handle_by_ref --- Processing reference: Hello, Stack Overflow! ❌ Destroying SmallType
Breaking Down the Difference
- Pass-by-value: When we pass
my_objtohandle_by_value, a full copy ofSmallTypeis created. This triggers the copy constructor, which has to allocate new heap memory and copy the entirestd::stringcontent—way more work than a simple bitwise copy. When the function finishes, the copied parameter is destroyed, adding another destructor call. - Pass-by-const-reference: When using pass-by-reference, no copy is made at all. We just reference the original object directly. No extra constructor/destructor calls, no heap allocation, no string copying—just a lightweight reference to the existing data.
Even Without Dynamic Memory, Costs Can Add Up
This example uses dynamic memory, but even small custom types without heap allocations can have hidden costs. For instance:
- If your type has member variables that are themselves user-defined types with expensive copies
- If your copy constructor includes side effects like logging, thread synchronization, or database calls
- If you’re passing the object frequently (e.g., in a loop), the cumulative cost of repeated copies becomes impossible to ignore
The key takeaway: Built-in types are cheap to copy because their copy is a trivial bitwise operation. User-defined types, however, can have arbitrary copy logic that makes pass-by-value expensive—even if they’re small in size.
内容的提问来源于stack exchange,提问作者Farsan Rashid

