Forum Replies Created

Viewing 15 posts - 211 through 225 (of 349 total)
  • Author
    Posts
  • in reply to: Data races by parallel STL #113031
    RainerRainer
    Keymaster
        Up
        0
        Down
        ::

        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];
            }
        
        };
        
        in reply to: enum class declaration #111145
        RainerRainer
        Keymaster
            Up
            0
            Down
            ::

            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.

             

            in reply to: Video Link for stlAlgorithmsTransform is broken #110664
            RainerRainer
            Keymaster
                Up
                0
                Down
                ::

                Thanks once more. I fixed it.

                in reply to: Video Link for stlAlgorithmsTransform is broken #110644
                RainerRainer
                Keymaster
                    Up
                    0
                    Down
                    ::

                    Thanks. Fixed it.

                    in reply to: Week 23 Introduction Section PDF Link Missing #110636
                    RainerRainer
                    Keymaster
                        Up
                        0
                        Down
                        ::

                        Thanks. Fixed it.

                        in reply to: Time Measurement in Milliseconds #109230
                        RainerRainer
                        Keymaster
                            Up
                            0
                            Down
                            ::

                            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;
                            
                            }
                            
                            in reply to: Using T or auto as a return type #109229
                            RainerRainer
                            Keymaster
                                Up
                                0
                                Down
                                ::

                                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 reply to: Time Measurement in Milliseconds #109016
                                RainerRainer
                                Keymaster
                                    Up
                                    0
                                    Down
                                    ::

                                    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.

                                    in reply to: enum class declaration #109015
                                    RainerRainer
                                    Keymaster
                                        Up
                                        0
                                        Down
                                        ::

                                        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.

                                        in reply to: Moving elements inside std::vector #108436
                                        RainerRainer
                                        Keymaster
                                            Up
                                            1
                                            Down
                                            ::
                                            1. 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.

                                            2. 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
                                            in reply to: Remove duplications from vector but keep the given order #108069
                                            RainerRainer
                                            Keymaster
                                                Up
                                                2
                                                Down
                                                ::

                                                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;
                                                }
                                                
                                                in reply to: enum class declaration #107818
                                                RainerRainer
                                                Keymaster
                                                    Up
                                                    0
                                                    Down
                                                    ::

                                                    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.
                                                    in reply to: Memory release by std::deque #107816
                                                    RainerRainer
                                                    Keymaster
                                                        Up
                                                        0
                                                        Down
                                                        ::

                                                        When you remove elements from a std::deque, its capacity is bigger than it’s size.  Now, a shrink_to_fit call may reduce capacity due to its size, but this all is not binding. This means, that a call shrink_to_fit does not have an effect on your system.

                                                        in reply to: Remove duplications from vector but keep the given order #107814
                                                        RainerRainer
                                                        Keymaster
                                                            Up
                                                            0
                                                            Down
                                                            ::

                                                            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.

                                                            in reply to: Moving elements inside std::vector #107813
                                                            RainerRainer
                                                            Keymaster
                                                                Up
                                                                0
                                                                Down
                                                                ::

                                                                Very good observation. Here are a few remarks:

                                                                1. The elements of a std::vector are on the heap and are stored in a contiguous memory block.
                                                                2. The containers of the STL have copy and not reference semantics. They copy all inside.
                                                                3. 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.

                                                                 

                                                              Viewing 15 posts - 211 through 225 (of 349 total)