C++ Tips to Reduce Extra Objects
C++ Tips to Reduce Extra Objects
Contents
• Introduction
• Basic strategies to avoid extra objects
• Strategies for std::string and std::vector
• Common STL types and containers
• Associative containers
• Transparent comparators
• Moving data to compile time
4
If our goal was to print the string, then the following code would suffice:
int main() {
std::cout << "Hello World!!\n";
}
We don’t need str and str2 to achieve our goal of printing the string.
Even with compiler optimization, the second code snippet generates much less code.
We consider these objects which are not necessary to achieve our goal as extra objects.
Not all scenarios for “extra” objects are so straightforward to detect.
5
The copy constructor of the string is called and that calls “operator new” to allocate memory.
MyClass c;
MyClass c1 = std::move(c); This statement calls the “move constructor” to create a new object.
int main() {
puts("==== Before initial string ======"); ==== Before initial string ======
std::string s("This is a hello world string!!"); operator new: n: 32, p = 0x5f0c2817b2b0
puts("==== Before move constructor ======"); ==== Before move constructor ======
const std::string s1 = std::move(s); ==== Before end ======
puts("==== Before end ======"); operator delete: p = 0x5f0c2817b2b0
}
The “move constructor” of string “does not” allocate memory, but just swaps memory.
Even though “move” creates a new object, it is not “costly” for runtime.
8
std::string GetStr() {
return "This is a hello world string!!";
}
==== Before GetStr() ======
operator new: n: 32, p = 0x5e83f9b412b0
int main() {
==== Before end ======
puts("==== Before GetStr() ======");
operator delete: p = 0x5e83f9b412b0
const std::string s = GetStr();
puts("==== Before end ======");
}
Creating objects in-place is preferrable over moving, which is preferrable over copying.
9
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6}; operator new: n: 24, p = 0x61b6931952a0
puts("==== Before move constructor ======"); ==== Before move constructor ======
const std::vector<int> v1(std::move(v)); ==== Before end ======
puts("==== Before end ======"); operator delete: p = 0x61b6931952a0
}
As a rule of thumb pass “non-trivial” “read-only” objects as const reference in arguments to functions.
void Foo(B b) {}
struct B { warning: the parameter 'b' is copied for each invocation but only used as a const reference;
B() = default; consider making it a const reference [performance-unnecessary-value-param]
void Foo(B b) {}
B(const B&);
^
B(B&&) noexcept = default; const &
};
From this documentation:
B::B(const B&) = default; The check is only applied to parameters of types that are expensive to copy which means they are not
trivially copyable or have a non-trivial copy constructor or destructor.
void Foo(B b) {}
Returning non-trivial member object 17
struct A final {
// Constructor struct A will be the placeholder for “non-trivial” object that we
A() { puts("A()"); } will use for most of this presentation.
A(int, int) { puts("A(int, int)"); }
When returning a non-trivial member variable from a member function, consider returning as const&.
Returning non-trivial member object 19
When the returned type is not held as const&, the copy constructor is still called to create an extra object.
Returning non-trivial member object 20
Return const& for a non-trivial type of member object being returned from a class's const member function
class MyClass final { int main() {
public: MyClass obj;
MyClass() : a_(10, 10) {}
const auto a = obj.a_not_great();
const A& a() const { return a_; }
const auto& a_ref = obj.a();
A a_not_great() const { return a_; } (void)a_ref; // Suppress warning.
// Suppress warnings.
(void)a_copy;
(void)obj2;
}
Use const auto& (or auto&&) in range based for loop traversal for non-trivial object container.
Range based for loop 22
struct A { error: loop variable 'a' creates a copy from type 'A const' [-Werror,-Wrange-loop-
void Foo() const {} construct]
void Bar() {} for (const auto a : a_vec) {
std::string str; ^
}; note: use reference type 'A const &' to prevent copying
for (const auto a : a_vec) {
^~~~~~~~~~~~~~
int main() { &
std::vector<std::string> vec;
for (auto v : vec) {
std::cout << v << '\n'; When clang-tidy check is used:
} --checks=performance-for-range-copy
// Vector of non-trivially
// copy constructable objects. warning: loop variable is copied but only used as const reference; consider making it a
std::vector<A> a_vec; const reference [performance-for-range-copy]
for (const auto a : a_vec) { for (auto v : vec) {
[Link](); ^
} const &
for (auto a : a_vec) { warning: the loop variable's type is not a reference type; this creates a copy in each
[Link](); iteration; consider making this a reference [performance-for-range-copy]
} for (const auto a : a_vec) {
} ^
&
24
NRVO is not “required” by standard. But most compilers implement it for such scenarios.
int main() {
std::ignore = Foo(); When we use: -Wpessimizing-move is included
} -Wpessimizing-move -Werror in -Wall
error: moving a local object in a return statement prevents copy elision [-Werror,-
Wpessimizing-move]
return std::move(a);
^
note: remove std::move call here
return std::move(a);
Scenario for explicit move on return 28
int main() {
std::ignore = Foo();
}
int main() {
std::ignore = Foo();
}
int main() {
std::ignore = Foo();
}
int main() {
std::ignore = Foo();
}
struct A {
A() { puts("A()"); }
~A() { puts("~A()"); }
A(const A&) { puts("A(const A&)"); }
A& operator=(const A&) {
puts("A& operator=(const A&)");
return *this;
}
A(A&&) noexcept { puts("A(A&&)"); }
A& operator=(A&&) noexcept {
puts("A& operator=(A&&)");
return *this;
}
auto GetCapture() {
return [*this]() { std::cout << "Inside lambda in GetCapture\n"; };
}
auto GetCapture1() {
return [this]() { std::cout << "Inside lambda in GetCapture1\n"; };
}
auto GetCapture2() {
return [&]() { std::cout << "Inside lambda in GetCapture2\n"; };
}
auto GetCapture3() {
return [=]() { std::cout << "Inside lambda in GetCapture3\n"; };
}
};
Lambda captures 32
struct A {
int a = 10;
auto GetCapture() {
return [*this]() {
std::cout << "Inside lambda in GetCapture: a: " << a << '\n';
}; error: implicit capture of 'this' with a capture default of '=' is
} deprecated [-Werror,-Wdeprecated-this-capture]
auto GetCapture1() {
return [this]() { std::cout << "Inside lambda in GetCapture3: a: " << a << '\n';
std::cout << "Inside lambda in GetCapture1: a: " << a << '\n';
};
}
auto GetCapture2() { Implicit capture of this via [=] was deprecated in
return [&]() { C++20 (P0806R2)
std::cout << "Inside lambda in GetCapture2: a: " << a << '\n';
};
}
auto GetCapture3() {
return [=]() {
std::cout << "Inside lambda in GetCapture3: a: " << a << '\n';
};
}
};
Lambda captures 34
std::string_view can handle std::string, const char* and {const char*, len} as arguments without the need to create different
functions for each. It does not do any heap allocation.
int main() { std::string_view can also be used to create the string at compile time instead
// Created at runtime. of runtime.
const std::string str("Hello");
// Created at compile time.
static constexpr std::string_view kStr("Hello");
}
37
string_view instead of string
Use std::string_view to stop creating std::string at runtime.
void Foo(const std::string& s) { // Platform function which expects null terminated string.
std::cout << "Foo: s: " << s << '\n'; void FooPlatform(const char* p);
}
void Foo(const std::string& s) {
void FooBetter(std::string_view s) { FooPlatform(s.c_str());
std::cout << "FooBetter: s: " << s << '\n'; }
}
Cannot use std::string_view in this case, since it may not be null terminated.
Instead of using std::string_view, we can follow the cpp core guideline: F.18: For "will-move-from"
parameters, pass by X&& and std::move the parameter.
struct A {
A(std::string&& str) : str_(std::move(str)) {}
// Other functions.
void SetStr(std::string&& str) { str_ = std::move(str); }
// Other functions.
std::string str_;
};
39
string_view instead of string
Sink scenario:
struct A { When the following clang-tidy check is used:
A(const std::string& str) : str_(str) {} --checks=modernize-pass-by-value
// Other functions.
void SetStr(const std::string& str) { str_ = str; } warning: pass by value and use std::move [modernize-pass-by-value]
// Other functions. A(const std::string& str) : str_(str) {}
std::string str_; ^~~~~~~~~~~~~~~~~~
}; std::string std::move( )
+ is only optimal if there is a single concatenation and either one of the parameters is std::string.
int main() {
std::string s; // Fill it in.
const auto sr = s + "right";
const auto sl = "left" + s;
}
42
Use string_view::substr to remove possible memory allocation
static constexpr std::string_view kHttp("[Link] int main() {
static constexpr std::string_view kHttps("[Link] const std::string str("[Link]
int main() {
constexpr int kTestSize = 4;
sizeof(A): 4
--- After `vec` creation ---
reserve ensures
std::cout << "sizeof(A): " << sizeof(A) << '\n'; operator new: size: 16 there’s a single
--- After `[Link](kTestSize)` ---
std::vector<A> vec;
std::cout << "--- After `vec` creation ---\n"; A(0) allocation and hence
[Link](kTestSize);
A(1)
A(2)
no temporaries
std::cout << "--- After `[Link](kTestSize)` ---\n";
for (int i = 0; i < kTestSize; ++i) {
A(3) during resize.
~A()
vec.emplace_back(i); ~A()
} ~A()
} ~A()
operator delete
44
int main() {
std::vector<A> v = {{1, 2}, {3, 4}, {5, 6}};
FooBetter(v);
std::array<A, 3> a = {A{1, 2}, A{3, 4}, A{5, 6}}; This creates “extra” objects as we will see in the
FooBetter(a);
std::initializer_list<A> l = {A{1, 2}, A{3, 4}, A{5, 6}}; later slides.
FooBetter(l);
FooBetter({{A{1, 2}, A{3, 4}, A{5, 6}}});
}
47
Use explicit std::move when a non-temporary object needs to be created.
Here’s an example for std::vector.
int main() { ==== Before non-move push_back ====
std::vector<A> vec; A(int, int)
[Link](2); A(const A&)
std::cout << "==== Before non-move push_back ====\n"; ~A()
{ ==== Before move push_back ====
A a(10, 10); A(int, int)
// Assume we update `a` based on some conditions. A(A&&)
vec.push_back(a); ~A()
} ==== Before end ====
std::cout << "==== Before move push_back ====\n"; ~A()
{ ~A()
A a(10, 10);
// Assume we update `a` based on some conditions.
vec.push_back(std::move(a)); // `move` is better here.
}
std::cout << "==== Before end ====\n";
}
In this scenario, since we cannot remove the “extra” “non-trivial” object, it is best to use std::move to “steal”
resources and gain performance.
48
int main() { A(int, int) Three objects are created and then copied into the
std::vector<A> v{{1, 1}, {2, 2}, {3, 3}}; A(int, int)
} A(int, int) vector
A(const A&)
A(const A&)
A(const A&)
~A()
~A()
~A()
~A()
~A()
~A()
reserve / emplace_back removes the need for temporary objects and instead does in-place construction.
std::pair 50
A(int, int)
int main() { A(int, int)
const std::pair<A, A> pa{{1, 1}, {2, 2}}; A(const A&)
} A(const A&)
~A()
~A()
~A()
~A()
There is no in-place construction for tuple. So, move instead of copy constructor is the best we can do.
int main() {
std::ignore = Foo();
}
For a non-trivial type being used in "success" type of std::expected, use in_place_t constructor to create the
object in place.
std::unexpected 55
For a non-trivial type being used in "error" type of std::expected, use unexpect_t constructor to create the error
object in place.
std::variant 56
For a non-trivial type being used in variant, use std::in_place_type or std::in_place_index constructor to
create the object in place.
std::variant: Changing value type 57
For a non-trivial type being used in variant, use emplace to change the object type contained in the variant.
std::variant: Changing value of existing type 58
A(int, int)
int main() { A(int, int)
const std::array<A, 2> arr = {A{5, 6}, A{7, 8}}; ~A()
} ~A()
A(int, int)
int main() { A(int, int)
// Uses deduction guide. ~A()
const std::array arr = {A{5, 6}, A{7, 8}}; ~A()
int a_ = 0;
};
Adding elements to vector 61
int main() {
constexpr int kTestSize = 2;
std::vector<A> vec;
[Link](kTestSize);
This check also works for std::stack, std::queue, std::deque, std::forward_list, std::list, std::priority_queue.
Use emplace* functions for in-place construction 63
struct A final {
A(int a) : a_(a) { printf("A(%d)\n", a_); } int main() {
~A() { puts("~A()"); } std::map<int, A> m;
A(const A& rhs) : a_(rhs.a_) { printf("A(const A&): %d\n", a_); } m[10] = 10;
A(A&& rhs) noexcept : a_(rhs.a_) { printf("A(A&&): %d\n", a_); } }
A& operator=(const A& rhs) {
a_ = rhs.a_;
printf("A& operator=(const A&): %d\n", a_);
return *this;
}
A& operator=(A&& rhs) noexcept {
a_ = rhs.a_;
printf("A& operator=(A&&): %d\n", a_);
return *this;
}
int a_ = 0;
};
67
For map, use emplace instead of operator[]
Let’s consider the case where the non-trivial object is value instead of key.
struct A final {
A() { puts("A()"); }
A(int a) : a_(a) { printf("A(%d)\n", a_); }
This paper (P2363) was accepted for C++26 and added template <typename K, typename... Args>
support for: std::pair<iterator, bool> try_emplace(K&& k, Args&&... args);
This will allow try_emplace to behave same as emplace in this scenario and will also let operator[] to construct in-place.
69
emplace for constructors with multiple arguments
struct B final { std::map<B, B> m;
// Needed for operator[]. [Link](10, 10, 20, 20); // COMPILATION ERROR.
B() { std::cout << "B(): " << GetStr() << '\n'; }
B(int i, int j) : v_(i, j) { std::cout << "B(i, j): " << GetStr() << '\n'; }
~B() { puts("~B()"); } B(i, j): (10, 10)
B(const B& rhs) : v_(rhs.v_) { B(i, j): (20, 20)
std::cout << "B(const B&): " << GetStr() << '\n'; B(B&&): (10, 10)
}
int main() {
B(B&& rhs) noexcept : v_(std::move(rhs.v_)) { std::map<B, B> m; B(B&&): (20, 20)
std::cout << "B(B&&): " << GetStr() << '\n'; [Link](B{10, 10}, B{20, 20}); ~B()
} } ~B()
B& operator=(const B& rhs) { ~B()
v_ = rhs.v_; ~B()
std::cout << "B& operator=(const B&): " << GetStr() << '\n';
return *this;
} int main() { B(i, j): (10, 10)
B& operator=(B&& rhs) noexcept { std::map<B, B> m; B(i, j): (20, 20)
v_ = std::move(rhs.v_); [Link](std::piecewise_construct, ~B()
std::cout << "B& operator=(B&&): " << GetStr() << '\n';
return *this; std::forward_as_tuple(10, 10), ~B()
} std::forward_as_tuple(20, 20));
std::string GetStr() const { }
std::stringstream ss;
ss << "(" << v_.first << ", " << v_.second << ")"; This approach calls the std::piecewise_construct constructor of
return [Link]();
} std::pair and gets in-place construction.
auto operator<=>(const B&) const noexcept = default;
For an existing key, the value type object is not created. However, this is not
guaranteed by emplace specification. try_emplace guarantees that behavior.
71
insert_or_assign instead of operator[].
template <const char* name>
struct Type {
Type() { printf("%s(): (%d, %d)\n", name, i, j); }
Type(int i, int j) : i(i), j(j) {
printf("%s(i, j): (%d, %d)\n", name, i, j);
}
~Type() { printf("~%s()\n", name); }
void operator delete(void* p) noexcept { We will check the methods count, contains and find.
printf("operator delete: p = %p\n", p);
free(p);
}
int main() {
----- Using count -----
std::set<std::string> s;
operator new: n: 40, p = 0x5b138382f2b0
constexpr char kTestStr[] = "Hello, do you contain this string?";
operator delete: p = 0x5b138382f2b0
std::cout << "----- Using count -----\n";
Count: 0
----- Using contains -----
const auto n = [Link](kTestStr);
operator new: n: 40, p = 0x5b138382f2b0
operator delete: p = 0x5b138382f2b0
std::cout << "Count: " << n << std::endl;
contains: Not found
std::cout << "----- Using contains -----\n";
----- Using find -----
operator new: n: 40, p = 0x5b138382f2b0
const auto found = [Link](kTestStr);
operator delete: p = 0x5b138382f2b0
find: Not found
if (!found) {
std::cout << "contains: Not found\n";
}
std::cout << "----- Using find -----\n";
auto it = [Link](kTestStr);
if (it == [Link]()) {
std::cout << "find: Not found\n";
}
}
std::set<std::string> 75
if (!found) {
std::cout << "contains: Not found\n";
}
std::cout << "----- Using find -----\n";
auto it = [Link](test_str);
if (it == [Link]()) {
std::cout << "find: Not found\n";
}
}
As we see, if we don’t use const std::string&, the functions count, contains, find will create temporary objects.
auto it = [Link](kTestStr);
if (it == [Link]()) {
std::cout << "find: Not found\n";
}
}
std::set<std::string>: transparent comparator 77
if (!found) {
std::cout << "contains: Not found\n";
}
std::cout << "----- Using find -----\n";
auto it = [Link](kTestStr);
if (it == [Link]()) {
std::cout << "find: Not found\n";
}
}
std::set<std::string>: transparent comparator 78
if (!found) {
std::cout << "contains: Not found\n";
}
std::cout << "----- Using find -----\n";
auto it = [Link](test_str);
if (it == [Link]()) {
std::cout << "find: Not found\n";
}
}
std::set<std::string> s;
Use std::less<> to get transparent comparison.
std::set<std::string, std::less<>> s;
Transparent comparator 79
Transparent comparators allow comparisons to happen without the need to create temporary objects in certain
scenarios.
They are identified by a typedef is_transparent.
Here's an example of std::less<void> specialization code from libc++ code.
template <>
struct _LIBCPP_TEMPLATE_VIS less<void> {
template <class _T1, class _T2>
_LIBCPP_CONSTEXPR_SINCE_CXX14 _LIBCPP_HIDE_FROM_ABI auto operator()(
_T1&& __t,
_T2&& __u) const
noexcept(noexcept(std::forward<_T1>(__t) < std::forward<_T2>(__u))) //
-> decltype(std::forward<_T1>(__t) < std::forward<_T2>(__u)) {
return std::forward<_T1>(__t) < std::forward<_T2>(__u);
}
typedef void is_transparent;
};
auto it = [Link](kTestStr);
if (it == [Link]()) {
std::cout << "find: Not found\n";
}
}
std::map<std::string, AnotherType>: Transparent comparator 81
if (!found) {
std::cout << "contains: Not found\n";
}
std::cout << "----- Using find -----\n";
auto it = [Link](kTestStr);
if (it == [Link]()) {
std::cout << "find: Not found\n";
}
}
std::map<std::string, int> m;
Use std::less<> to get transparent comparison.
auto it = [Link](kTestStr);
if (it == [Link]()) {
std::cout << "find: Not found\n";
}
}
std::unordered_set<std::string>: Transparent comparator 83
std::unordered_set<std::string> s;
Use MyStringHash and std::equal_to to get transparent comparison.
auto it = [Link](kTestStr);
if (it == [Link]()) {
std::cout << "find: Not found\n";
}
}
unordered_map<string, OtherType> : Transparent comparator 85
if (!found) {
std::cout << "contains: Not found\n";
}
std::cout << "----- Using find -----\n";
auto it = [Link](kTestStr);
if (it == [Link]()) {
std::cout << "find: Not found\n";
}
}
std::unordered_map<std::string, int> m;
Use MyStringHash and std::equal_to to get transparent comparison.
std::set<std::string> s;
std::set<std::string, std::less<>> s;
std::map<std::string, int> m;
std::unordered_set<std::string> s;
std::unordered_map<std::string, int> m;
Runtime const std::string / std::vector: Can be moved to compile time using std::string_view / std::array:
int main() { int main() {
const std::string str("hello"); static constexpr std::string_view kStr("hello");
const std::vector arr{1, 2, 3}; static constexpr char kStrArr[] = "hello";
} static constexpr int kCArr[] = {1, 2, 3};
static constexpr auto kArr = std::to_array({1, 2, 3});
}
90
These flags help in figuring out opportunities for moving global const objects to compile time.
int main() {}
91
int main() {}
// Source file.
namespace { // Header
const std::vector global_arr{1, 2, 3}; std::span<const int> GetVec();
} // namespace
// Source file.
const std::vector<int>& GetVec() {
namespace {
return global_arr;
constexpr std::array kArr{1, 2, 3};
}
} // namespace
// Source file
namespace {
struct SomeClass {
std::string str1;
std::string str2;
int value;
};
// Source file
namespace {
struct SomeClass {
std::string str1;
std::string str2;
int value;
};
absl::NoDestructor can also better “annotate” this “will not delete” scenario.
95
Moving User defined “const” data structures to compile time
// Header.
bool ContainsStr1(std::string_view str);
// Source file
namespace {
struct SomeClass {
const std::string_view str1;
const std::string_view str2;
const int value;
};
In our code base, we have seen quite a few instances where user defined data
structures could be converted to compile time.
96
int main() {
constexpr std::string_view kHello = "Hello, "; This is a runtime string created which will always be the same.
constexpr std::string_view kWorld = "World!!";
const std::string result = MyStrCat({kHello, kWorld});
std::cout << result << std::endl;
}
int main() {
static constexpr std::string_view kHello = "Hello, ";
static constexpr std::string_view kWorld = "World!!";
static constexpr std::string_view kJoinResultStr =
MyCompileTimeStringJoinerV<kHello, kWorld>;
static_assert(kJoinResultStr == "Hello, World!!");
std::cout << kJoinResultStr << std::endl;
}
97
Chromium has fixed_flat_map and fixed_flat_set which can be used to create compile time set / map equivalents.
std::flat_map and std::flat_set will be made constexpr in C++26. Once constexpr, some additional code can be
written to create instances at compile time.
99
Conclusion
100
Key Points
• We want in-place construction without copies or moves.
• Pass non-trivial objects by reference
• Use view types (std::string_view, std::span)
• Use in-place constructors for STL types
• Use emplace
• Use transparent comparators for std::string in associative
containers
• Move data to compile time
• Use clang-tidy checks and warnings
101
Thank you!
Special thanks to Chandranath Bhattacharyya!
102
References
• CppCon 2024: How to Use string_view in C++ - Basics, Benefits, and Best Practices -
Jasmine Lopez & Prithvi Okade.
• CppCon 2018: Jon Kalb “Copy Elision”.
• CppCon 2024: C++ RVO: Return Value Optimization for Performance in Bloomberg C++
Codebases - Michelle Fae D'Souza.
• CppCon 2017: Jorg Brown “The design of absl::StrCat...”
• Understanding The constexpr 2-Step - Jason Turner - C++ on Sea 2024.
• C++Now 2018: Jason Turner “Initializer Lists Are Broken, Let's Fix Them”.
• C++ Weekly - Ep 421 - You're Using optional, variant, pair, tuple, any, and expected
Wrong!
• Why is there no piecewise tuple construction?
• CppCon 2018: Andrei Alexandrescu “Expect the expected”.
• In-Place Construction for std::any, std::variant and std::optional: Bartlomiej Filipek
([Link]).
103
References
• is_transparent: How to search a C++ set with another type than its key: Jonathan
Boccara ([Link]).
• Overview of std::map’s Insertion / Emplacement Methods in C++17 - Fluent C++
• c++ - How to concatenate static strings at compile time? - Stack Overflow
• P2363R3: Extending associative containers with the remaining heterogeneous
overloads
• C++ Core Guidelines
• clang-tidy checks
• Diagnostic flags in Clang
• absl::StrCat
• absl::NoDestructor
• Chromium: fixed_flat_map.h, fixed_flat_set.h
Microsoft @ CppCon
Monday, Sept 15 Tuesday, Sept 16 Wednesday, Sept Thursday, Sept 18 Friday, Sept 19
17
Building Secure Applications: A What’s New for Visual Studio LLMs in the Trenches: MSVC C++ Dynamic Debugging: Reflection-based JSON in C++
Practical End-to-End Approach Code: Cmake Improvements and Boosting System How We Enabled Full at Gigabytes per Second
GitHub Copilot Agents Programming with AI Debuggability of Optimized
Code
Chandranath Bhattacharyya & Bharat Alexandra Kemper Ion Todirel Eric Brumer Daniel Lemire & Francisco Geiman
Kumar 09:00 – 10:00 14:00 – 15:00 14:00 – 14:30 Thiesen
16:45 – 17:45 09:00 – 10:00
What’s new in Visual Studio for C++ Performance Tips: It’s Dangerous to Go Alone: A Duck-Tape Chronicles:
C++ Developers in 2025 Cutting Down on Game Developer Tutorial Rust/C++ Interop
Unnecessary Objects
Augustin Popa & David Li Kathleen Baker & Prithvi Okade Michael Price Victor Ciura
14:00 – 15:00 15:15 – 16:15 16:45 – 17:45 13:30 – 14:30
Questions?
[Link]
106
Appendix:
MyStrCat
StrCat: Another implementation 107
template <typename>
using StringViewType = std::string_view;
Appendix:
Transparent Comparators
User Defined Types
Transparent comparator for user-defined types 110
struct A final {
A(int a) : a_(a) { printf("A(%d)\n", a_); }
~A() { puts("~A()"); }
}
std::cout << "Not Found\n";
We will check the methods count, contains
std::cout << "------ After find ------\n"; and find.
}
std::set<UserType>: Transparent comparator 112
struct A final {
A(int a) : a_(a) { printf("A(%d)\n", a_); }
~A() { puts("~A()"); }
auto operator<=>(const A&) const noexcept = default; This allows A to compare with int.
auto operator<=>(int i) const noexcept { return a_ <=> i; }
int a_ = 0;
};
std::set<UserType>: Transparent comparator 114
if (!found) {
std::cout << "contains: Not found\n";
}
std::cout << "------ Using find ------\n";
auto it = [Link](10);
if (it == [Link]()) {
std::cout << "Not Found\n";
}
std::cout << "------ After find ------\n";
}
if (!found) {
std::cout << "contains: Not found\n";
}
std::cout << "------ Using find ------\n";
auto it = [Link](10);
if (it == [Link]()) {
std::cout << "Not Found\n";
}
std::cout << "------ After find ------\n";
}
std::unordered_set<UserType> 118
struct A final {
A(int a) : a_(a) { printf("A(%d)\n", a_); }
~A() { puts("~A()"); }
bool operator==(const A&) const noexcept = default; These are necessary for code to compile for A
int a_ = 0; to be used as key in unordered_set:
}; std::unordered_set<A>
template <>
struct std::hash<A> {
std::size_t operator()(const A& a) const noexcept {
return std::hash<int>{}(a.a_);
}
};
std::unordered_set<UserType> 119
int a_ = 0;
};
std::unordered_set<UserType>: Transparent comparator 122
A& operator=(A&& rhs) noexcept { These are needed to ensure “transparent operators”
a_ = rhs.a_; work properly for user defined types as “key” of
printf("A& operator=(A&&): %d\n", a_);
return *this; std::unordered_set.
}
int a_ = 0;
};
std::unordered_set<UserType>: Transparent comparator 123
if (!found) {
std::cout << "contains: Not found\n";
}
std::cout << "------ Using find ------\n";
auto it = [Link](10);
if (it == [Link]()) {
std::cout << "Not Found\n";
}
std::cout << "------ After find ------\n"; template <>
}
struct std::hash<A> {
std::size_t operator()(const A& a) const noexcept {
struct A final { return std::hash<int>{}(a.a_);
// << snipped >> }
bool operator==(const A&) const noexcept = default; std::size_t operator()(int i) const noexcept {
return std::hash<int>{}(i);
bool operator==(int i) const noexcept { return a_ == i; } }
// << snipped >> using is_transparent = void;
}; };
std::unordered_map<UserType, T> 125
auto it = [Link](10);
if (it == [Link]()) {
std::cout << "Not Found\n";
}
std::cout << "------ After find ------\n";
}
std::unordered_map<UserType, T>: Transparent comparator 126
if (!found) {
std::cout << "contains: Not found\n";
}
std::cout << "------ Using find ------\n";
auto it = [Link](10);
if (it == [Link]()) {
std::cout << "Not Found\n";
}
std::cout << "------ After find ------\n";
}
127
Appendix:
Compile Time set/map
Compile time set 128
private:
std::array<PairType, N> data_;
};
Compile time map constexpr std::optional<Value> GetValue(const Key& elem) const {
const auto [start, end] =
129
Appendix:
-Wlarge-by-value-copy
-Wlarge-by-value-copy 132
#include <iostream>
#include <string>
When compiled with -Wlarge-by-value-
struct A { copy=24
int a, b, c, d; This flags sizes > 24.
int e, f, g;
};
error: '' is a large (28 bytes) pass-by-value
struct B { argument; pass it by reference instead ? [-Werror,-
std::string a; Wlarge-by-value-copy]
std::string b; void Foo(A) {}
}; ^
void Foo(A) {}
It only catches PODs, so does not catch
void Foo(B) {} Foo(B).
int main() {
std::cout << "sizeof(A): " << sizeof(A) << '\n'; // 28 It is “not” on by default.
std::cout << "sizeof(B): " << sizeof(B) << '\n'; // 48
std::cout << "sizeof(std::string): " << sizeof(std::string) << '\n'; // 24
std::cout << "sizeof(std::string_view): " << sizeof(std::string_view)
<< '\n'; // 16
}