- Function definitions
- Parameter passing: in and out
- Parameter passing: ownership semantics
- Value return semantics
- Other functions
- Related rules
Parameter passing: ownership semantics
The last section was about the flow of parameters: which parameters are input, input/output, or output values. But there is more to arguments than the direction of the flow. Passing parameters is about ownership semantics. This section presents five typical ways to pass parameters: by copy, by pointer, by reference, by std::unique_ptr, or by std::shared_ptr. Only the rules to smart pointers are inside this section. The rule to pass by copy is part of the previous section Parameter Passing: In and Out, and the rules to pointers and references are part of Chapter 3, Interfaces.
Table 4.3 provides the first overview.
Table 4.3 Ownership semantics of parameter passing
Example |
Ownership |
Rule |
---|---|---|
func(value) |
func is a single owner of the resource. |
F.16 |
func(pointer*) |
func has borrowed the resource. |
I.11 and F.7 |
func(reference&) |
func has borrowed the resource. |
I.11 and F.7 |
func(std::unique_ptr) |
func is a single owner of the resource. |
F.26 |
func(std::shared_ptr) |
func is a shared owner of the resource. |
F.27 |
Here are more details:
func(value): The function func has its own copy of the value and is its owner. func automatically releases the resource.
func(pointer*): func has borrowed the resource and is, therefore, not authorized to delete the resource. func has to check before each usage that the pointer is not a null pointer.
func(reference&): func has borrowed the resource. In contrast to the pointer, the reference always has a valid value.
func(std::unique_ptr): func is the new owner of the resource. The caller of the func has explicitly transferred the ownership of the resource to the callee. func automatically releases the resource.
func(std::shared_ptr): func is an additional owner of the resource. func extends the lifetime of the resource. At the end of func, func ends its ownership of the resource. This end causes the release of the resource if func was the last owner.
Here are five variants of ownership semantics in practice.
1 // ownershipSemantic.cpp 2 3 #include <iostream> 4 #include <memory> 5 #include <utility> 6 7 class MyInt { 8 public: 9 explicit MyInt(int val): myInt(val) {} 10 ~MyInt() noexcept { 11 std::cout << myInt << '\n'; 12 } 13 private: 14 int myInt; 15 }; 16 17 void funcCopy(MyInt myInt) {} 18 void funcPtr(MyInt* myInt) {} 19 void funcRef(MyInt& myInt) {} 20 void funcUniqPtr(std::unique_ptr<MyInt> myInt) {} 21 void funcSharedPtr(std::shared_ptr<MyInt> myInt) {} 22 23 int main() { 24 25 std::cout << '\n'; 26 27 std::cout << "=== Begin" << '\n'; 28 29 MyInt myInt{1998}; 30 MyInt* myIntPtr = &myInt; 31 MyInt& myIntRef = myInt; 32 auto uniqPtr = std::make_unique<MyInt>(2011); 33 auto sharedPtr = std::make_shared<MyInt>(2014); 34 35 funcCopy(myInt); 36 funcPtr(myIntPtr); 37 funcRef(myIntRef); 38 funcUniqPtr(std::move(uniqPtr)); 39 funcSharedPtr(sharedPtr); 40 41 std::cout << "==== End" << '\n'; 42 43 std::cout << '\n'; 44 45 }
The type MyInt displays in its destructor (lines 10–12) the value of myInt (line 14). The five functions in the lines 17–21 implement each of the ownership semantics. The lines 29–33 have the corresponding values. See Figure 4.3.
Figure 4.3 The five ownership semantics
The screenshot shows that two destructors are called before and two destructors are called at the end of the main function. The destructors of the copied myInt (line 35) and the moved uniquePtr (line 38) are called before the end of main. In both cases, funcCopy or funcUniqPtr becomes the owner of the resource. The lifetime of the functions ends before the lifetime of main. This end of the lifetime does not hold for the original myInt (line 29) and the sharedPtr (line 33). Their lifetime ends with main, and therefore, the destructor is called at the end of the main function.