An interesting concept comes up from Rust's borrow ownership.
In C++, the unique_ptr can only be moved. While instead of using heavy-weighted shared_ptr
(i.e 2 words size, heap allocated control block, atomic ref-counting, weak_ptr etc.), the only way of borrow the ownership from unique_ptr is by passing raw pointer from unique_ptr.
Here's the tweet from Prof. John Regehr talking about this:
https://twitter.com/johnregehr/status/1351333748277592065
Follow up from s.o:
https://stackoverflow.com/a/30013891
i.e in C++, the borrow concept can't be made in compile time, but in runtime, which unlike Rust.
Another to mention about is trivial_abi.
AMD64 ABI for C++: https://www.uclibc.org/docs/psABI-x86_64.pdf
While call by value,
Quote from ABI:
If a C++ object has either a non-trivial copy constructor or a non-trivial destructor, it is passed by invisible reference (the object is replaced in the parameter list by a pointer […]).
And in Itanium C++ ABI document, quote:
If the parameter type is non-trivial for the purposes of calls, the caller must allocate space for a temporary and pass that temporary by reference.
A type is considered non-trivial for the purposes of calls if: it has a non-trivial copy constructor, move constructor, or destructor, or all of its copy and move constructors are deleted.
That is call by value with non-trivial type introduce double indirection during the call while the type could potentially not fit into the register for callee.
i.e potential virtual pointer to v-table increases the size of type.
Reference: https://www.raywenderlich.com/615-assembly-register-calling-convention-tutorial
Assembly code can be found in godbolt:
https://godbolt.org/z/W6njsx
#include <cstdio>
#define TRIVIAL_ABI __attribute__((trivial_abi))
template <class T> T incr(T obj) {
obj.value += 1;
puts("before exit incr func");
return obj;
}
struct Up1 {
int value;
Up1() = default;
Up1(const Up1& u) : value(u.value) { puts("Up1 copy constructor"); }
~Up1() { printf("detroyed Up1 value: %d\n", value); }
};
struct TRIVIAL_ABI Up2 {
int value;
Up2() = default;
Up2(const Up1& u) : value(u.value) { puts("Up2 copy constructor"); }
~Up2() { printf("detroyed Up2 value: %d\n", value); }
};
template Up1 incr(Up1);
template Up2 incr(Up2);
auto main() -> int {
auto u1 = Up1{};
puts("before call incr func for u1");
incr(u1);
printf("\n\n");
auto u2 = Up2{};
puts("before call incr func for u2");
incr(u2);
}
Output:
before call incr func for u1
Up1 copy constructor
before exit incr func
Up1 copy constructor
detroyed Up1 value: 1
detroyed Up1 value: 1
before call incr func for u2
before exit incr func
detroyed Up2 value: 1
detroyed Up2 value: 1
detroyed Up2 value: 0
detroyed Up1 value: 0
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.