Forum Replies Created
-
AuthorPosts
-
::
Here is my mindset: When an operation makes no thread-safety guarantee, you should consider it as not thread-safe.
You can use the following strategy:
“We can solve any problem by introducing an extra level of indirection.” (https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering). Put your operations into a const member function. No, the c++ runtime guarantees that the operation does not change anything on the std::unordered_map: https://godbolt.org/z/haTvoc7Gb.
struct Test{ std::unordered_map<int, int> mySet{}; void nonConstMember() { mySet.find(5); mySet[5]; } void constMember() const { mySet.find(5); mySet[5]; } };
::I found a nice example. Consider the following function (https://godbolt.org/z/573YG1ceh):
#include <iostream> #include <cstdlib> int main( ){ char right[] = "0.0"; char* res1; double d1 = std::strtod(right, &res1); std::cout << d1 << '\n'; // 0 char wrong[] = "not a number"; char* res2; double d2 = std::strtod(wrong, &res2); std::cout << d2 << '\n'; // 0 }
d1 and d2 have the same value 0.0, but the value of d2 is due to a conversion error. The caller of std::strtod cannot decide if the input of the function was “0.0” or coud not be converted.
::When you subtract two time points, you get a time duration. The default time duration is seconds, but you can easily display it in different time durations such as millisecond, minutes, or hours. In the following program on Compiler Explorer, I add time durations and display the result in various resolutions, starting with seconds:
#include <iostream> #include <chrono> using namespace std::literals::chrono_literals; int main(){ std::cout << std::endl; constexpr auto schoolHour= 45min; constexpr auto shortBreak= 300s; constexpr auto longBreak= 0.25h; constexpr auto schoolWay= 15min; constexpr auto homework= 2h; constexpr auto schoolDayInSeconds= 2*schoolWay + 6 * schoolHour + 4 * shortBreak + longBreak + homework; std::cout << "School day in seconds: " << schoolDayInSeconds.count() << std::endl; std::chrono::duration<double,std::ratio<3600>> schoolDayInHours = schoolDayInSeconds; std::chrono::duration<double,std::ratio<60>> schoolDayInMinutes = schoolDayInSeconds; std::chrono::duration<double,std::ratio<1,1000>> schoolDayInMilliseconds= schoolDayInSeconds; std::cout << "School day in hours: " << schoolDayInHours.count() << std::endl; std::cout << "School day in minutes: " << schoolDayInMinutes.count() << std::endl; std::cout << "School day in milliseconds: " << schoolDayInMilliseconds.count() << std::endl; std::cout << std::endl; }
::When you know the concrete type, T is fine. If not, such as in your example for the generic add function, you have to use auto.
For example, a + b should return the same value as b + a. You cannot achieve this with a concrete type T.
#include <iostream> template<typename T, typename U> T add1(T first, U second) { return first + second; } template<typename T, typename U> auto add2(T first, U second) { return first + second; } int main(){ std::cout << add1(5, 5.5) << '\n'; // 10 <= std::cout << add1(5.5, 5) << '\n'; // 10.5 std::cout << add2(5, 5.5) << '\n'; // 10.5 std::cout << add2(5.5, 5) << '\n'; // 10.5 }
::In general, you should use std::chrono::steady_clock, because a std::chrono::system_clock can be adjusted while you measure. The following post shows you the accuracy of the clocks depending on your operating system: https://www.modernescpp.com/index.php/the-three-clocks.
::Consider the following function:
int getTemperature() { // ... return {}; }
Assume the function returns a temperature. Sometimes, the sensor does not work, and you return in this case a 0 ( return {} ). In one point in the future, 0 may be a valid value. Now, you have an issue.
It is critical, to use a special value such as 0, empty string, or a negative value to indicate no valid value.
::- This rule is called the rule of Zero/Six. Either you implement all of them or neither. Your implementation is inconsistent. See the following simplified example:
#include <vector> #include <iostream> struct MyClass { MyClass(int i) : myInt(new int(i)) { std::cerr << "created\n"; } int *myInt; MyClass(const MyClass& other) : myInt(new int(*other.myInt)) { std::cerr << "copy constructor\n"; } MyClass(MyClass&& other) : myInt( std::move(other.myInt) ) { other.myInt = nullptr; // (1) std::cerr << "move constructor\n"; } ~MyClass() { std::cerr << "destructor " << this << "\n"; delete myInt; } }; int main() { MyClass a( 10); std::vector< MyClass > myVec; std::cerr << "before\n"; myVec.push_back( std::move( a ) ); // (2) std::cerr << "after\n"; }
In this example, the destructor is called twice: for a and for myVec. Consequentially, I have to set the moved-from pointer into a valid state: a null pointer. Deleting a null pointer is safe. Here is the program on Compiler Explorer: https://godbolt.org/z/zj3jbEWEW. Your example is exactly the reason why I suggest in my classes to use a smart pointer such as std::unique_ptr or std::shared_ptr inside a class. Using a pointer is pretty tricky.
- Calling std::move on an object puts it in a so-called moved-from state.
- Moved-from objects are in a valid but unspecified state. Valid state means that all the requirements the standard specifies for the type still hold true. Essentially, you don’t have undefined behavior, but you don’t know in which state the moved-from object is. This has a simple consequence. You must put the object in a specified state. ⇒ initialize a after line (2) if you use it
12. November 2022 at 07:44 in reply to: Remove duplications from vector but keep the given order #108069::Your copy-if variants (https://godbolt.org/z/v96c1Enfq) are very smart, and I like them:
#include <vector> #include <algorithm> #include <unordered_map> #include <unordered_set> #include <iostream> int main() { std::vector<int> arr1 = {1, 2, 5, 1, 1, 3, 2, 6, 5, 4, 7}; std::vector<int> arr2, arr3, arr4; std::unordered_map<int,int> dups; std::copy_if(arr1.begin(), arr1.end(), std::back_inserter(arr2), // (1) [&dups](int a){ return dups[a]++ == 0; }); std::copy_if(arr1.begin(), arr1.end(), std::back_inserter(arr3), // (2) [dups2=std::unordered_map<int,int>() ](int a) mutable { return dups2[a]++ == 0; }); std::copy_if(arr1.begin(), arr1.end(), std::back_inserter(arr4), // (3) [dups3=std::unordered_set<int>() ](int a) mutable { return dups3.insert(a).second==true; }); for (int i: arr2) { std::cout << i << " "; } std::cout << std::endl; for (int i: arr3) { std::cout << i << " "; } std::cout << std::endl; for (int i: arr4) { std::cout << i << " "; } std::cout << std::endl; }
All three variants have linear complexity O(n): You iterate only once through the vector and apply a constant operation on the unordered associative containers std::unordered_map and std::unordered_set.
From the readability perspective, version 3 is probably the best one. On the contrary, you can use version 1 or 2 are more general, and you can remove all elements that appear more than n times. I don’t like about version 1 that you pollute the surrounding scope with the std::unordered_map. You solved this issue by using a lambda capture initializer. This is a C++14 feature. With C++11, an additional scope would also do the job:
{ std::unordered_map<int,int> dups; std::copy_if(arr1.begin(), arr1.end(), std::back_inserter(arr2), // (1) [&dups](int a){ return dups[a]++ == 0; }); }Additionally: When you implement such a smart algorithm, write a wrapper around it, such as a function. The function name should serve as additional documentation. The most important part of your job is it in this case to find a good name for the function.
#include <vector> #include <algorithm> #include <unordered_set> #include <iostream> void copyStableWithoutDuplicates(const std::vector<int>& from, std::vector<int>& to) { std::copy_if(from.begin(), from.end(), std::back_inserter(to), [dups3=std::unordered_set<int>() ](int a) mutable { return dups3.insert(a).second==true; }); } int main() { std::vector<int> arr1 = {1, 2, 5, 1, 1, 3, 2, 6, 5, 4, 7}; std::vector<int> arr4; copyStableWithoutDuplicates(arr1, arr4); for (int i: arr4) { std::cout << i << " "; } std::cout << std::endl; }
::I know what you meant with creating the enum inside or outside the class. My point was that I don’t know your use-case. In general, you should do it inside the class. This make your code more encapsulated and readable.
A few remarks about your code.
- return 0 in main is not necessary. The compiler will do it automatically.
- return {} to indicate no value is critical. Some time in the future, the empty string maybe a valid result. Additionally, the caller can ignore to check for the empty string and use it. Probably an exception or std::optional is a better choice.
11. November 2022 at 20:37 in reply to: Remove duplications from vector but keep the given order #107814::A modification algorithm which keeps the order of its elements is called stable. Compare std::sort with std::stable_sort. If you want to apply more than one sort operation on a data structure, it must be stable.
::Very good observation. Here are a few remarks:
- The elements of a std::vector are on the heap and are stored in a contiguous memory block.
- The containers of the STL have copy and not reference semantics. They copy all inside.
- When you directly create your object in the push_back (vec.push_back(5)) call, it’s directly created inside the contiguous memory block on the heap.
- This rule is called the rule of Zero/Six. Either you implement all of them or neither. Your implementation is inconsistent. See the following simplified example:
-
AuthorPosts
