Forum Replies Created
-
AuthorPosts
-
::
Option 2 is in general a bad idea.
You cannot be sure that the compiler generated copy constructor (= default) have the same semantic as your user-defined move constructor. For example, your use-defined move constructor sets other._data = nullptr, but the compiler generated move constructor not.
MyData( MyData&& other ) noexcept : _i( other._i ), _data( other._data ) { std::cout << "I am move Ctor" << std::endl; other._i = 0; other._data = nullptr; }; // MyData( MyData&& other ) = default could essentially do the following MyData( MyData&& other ) noexcept : _i( other._i ), _data( other._data ) {}
Now, a user-defined destructor could terribly fail due to double delete.
~MyData() noexcept { if (_data != nullptr) delete[] data_; }
Here is my contrived example: https://godbolt.org/z/Yn63x1chz
Either =default all of them or define all of them.
::I’m not sure if this rule of thumb always apply. Meaning, I may fix them later.
- Use std::move(lval) when you want to transfer the ownership of an object. In this case, lval is a lvalue.
- Don’t use std::move(rval), when rval is already an rvalue such as in your examples (temporary, and return statement). You can only suppress move semantics by using std::move.
::Let me directly answer your questions:
Foo& Foo::operator=(Foo&& a) noexcept { s = std::move(a.s); i = a.i; return *this; }
- After applying the move constructor, a is in valid but unspecified state. I base my implementation on the move constructor of std::string, and the copy constructor of
- This is a very controversial discussion. Here is the reasoning of Howard Hinnard, who design move semantics:
- When an object binds to an rvalue reference it is one of two things:
- A temporary.
- An object the caller wants you to believe is a temporary. (e.g.: using std::move)
- After the move assignment operation, the moved-from object must be in a valid but unspecified state. This means, that the check for self-assignment adds no value. It only cost a bit. Consequentially, I don’t do it.
- When an object binds to an rvalue reference it is one of two things:
Here is an additional point, you should keep in mind, when you apply the move assignment operator that is often forgotten. You are already the owner of some resources. Consequentially, you have to delete your existing data:
Foo& Foo::operator=(Foo&& a) noexcept { 1. Delete existing data 2. Move state from other 3. Set state from other in a "valid but unspecified state" }
For the move constructor, only steps 2 and 3 are necessary.
::You have to distinguish the C++ string and the C-string, the C++ string owns.
#include <iostream> #include <string> int main() { std::string str1{ "ABCDEF" }; std::string str2; std::cout << "str1: " << str1 << '\n'; std::cout << "&str1: " << &str1 << '\n'; std::cout << "str1.size(): " << str1.size() << '\n'; std::cout << "str1.data(): " << str1.data() << '\n'; std::cout << "\n\n"; str2 = std::move(str1); std::cout << "str1: " << str1 << '\n'; std::cout << "&str1: " << &str1 << '\n'; std::cout << "str1.size(): " << str1.size() << '\n'; std::cout << "str1.data(): " << str1.data() << '\n'; }
- C++ string: str1
- C string, that str1 owns: str1.data()
Here is the corresponding program: https://godbolt.org/z/4zEhnGc8a
::This book is the definite guide for concurrency in C++. This means, it’s very accurate and very challenging, in particular, when you read the parts about atomics and lock-free data structures.
C++20 brought significant additions to concurrency.
- std::atomic_ref
- atomic shared pointer
- std::atomic<floating point values>
- Waiting on atomics
- Semaphores, latches, and barriers
- std::jthread
- Coroutines (workflow related)
Here is an overview of the post I have published about C++20 including concurrency.
::Here is the short answer. The longer will follow in week 7 (memory) and week 18 (smart pointers)
- The size of the stack is limited (roughly 2 megabyte). Heap has typically a few gigabytes. Compare the size of the std::size and the std::vector in my post “Copy versus Move Semantics: A few Numbers”
- Your object’s lifetime should not be bound to a local scope (e.g.: function). A typical example is a factory. A factory creates objects and returns them to the caller.
When you need new and delete, here is the rule of thumb: nnn (no naked new) => Your new call should be performed inside a guard like std::unique_ptr.
::Honestly, this example is too complicated at that point in time. I need to explain a few additional features first:
- Type traits (topic of the mentoring)
- std::variant and std::visit (a type safe union in C++17: https://en.cppreference.com/w/cpp/utility/variant)
- constexpr if (no topic of this course, but I wrote about it: https://www.modernescpp.com/index.php/constespr-if)
- Visitor pattern (one of the classical design pattern: https://en.wikipedia.org/wiki/Visitor_pattern)
Your example is a nice example, when I would make a deep dive into programming at compile time or designs patterns with modern C++. I will do both in the future.
Here are the essential posts you can read:
- constexpr if: https://www.modernescpp.com/index.php/constespr-if
- std::variant and std::visit: https://www.modernescpp.com/index.php/c-17-constructed-in-place
- std::variant and the overload pattern: https://www.modernescpp.com/index.php/visiting-a-std-variant-with-the-overload-pattern
I don’t see the point of your question: ” Maybe discuss the std::variant’s std::visit() call, how the if branches are created in compile time, while the objects are in runtime…” This is always happening. An if or case statement is created at compile time, but used at run time. In case of a constexpr if, you only created it when the condition holds.
::Let me clarify this.
struct MyStruct { void constMemberFunction() const {} void nonConstMemberFunction() {} }; int main() { const MyStruct myConstStruct; MyStruct myNonConstStruct; myConstStruct.constMemberFunction(); myNonConstStruct.constMemberFunction(); myConstStruct.nonConstMemberFunction(); // Error: discards const qualifier myNonConstStruct.nonConstMemberFunction(); }
A const instance can invoke only const member functions.
A non-const instance can invoke const and non-const member functions.
::When you need your vector at run time, you cannot use constexpr. This idea of a constexpr vector is that you do your operations at compile time. Additionally, when you need 10’000 such vectors, there is no way to overcome this. A std::vector needs on my platform 24 bytes more than a C-Array or a std::array: https://www.modernescpp.com/index.php/c-core-guidelines-the-standard-library
The only solution I see for your vectors is to share data. This is possible if the vectors are immutable.
::Very good poin: decltype does not evaluate its value. => decltype is a so-called unevaluated operand: https://humanreadablemag.com/issues/0/articles/what-are-unevaluated-operands-in-c
::I assume, you mean to following program:
// dynamic_cast #include <iostream> #include <exception> using namespace std; class Base { virtual void dummy() {} }; class Derived: public Base { int a; }; int main () { try { Base * pba = new Derived; Base * pbb = new Base; Derived * pd; pd = dynamic_cast<Derived*>(pba); if (pd==0) cout << "Null pointer on first type-cast.\n"; pd = dynamic_cast<Derived*>(pbb); if (pd==0) cout << "Null pointer on second type-cast.\n"; } catch (exception& e) {cout << "Exception: " << e.what();} return 0; }
In this example, the downcast pd = dynamic_cast<Derived*>(pba); works, because pba is a pointer to Derived and Base is a polymorphic type. This does not hold for the second example. pbb is a Pointer to Base and not to Derived.
::- This is tricky. With C++11, a constexpr function is const ( https://godbolt.org/z/7bjYMYP8Y). This does not hold for C++14: a constexpr function in not const. Consequentially, you have to declare getVal as const to use it with a constexpr object.
- decltype(mDoble2) is const MyDouble, but MyDouble is not const. This works: static_assert(std::is_same_v< decltype(mDoble2), const MyDouble >, “Types not the same: mDoble3 and MyDouble”);
- mDoble3 is not const
-
AuthorPosts