Search code examples
c++c++11vectormove

Replace class new[] variables with vectors - move, copy operators


I made a sparse matrix class for some work I am doing. For the sparse structures, I used pointers, e.g. int* rowInd = new int[numNonZero]. For the class I wrote copy and move assignment operators and all works fine.

Reading about the move and copy semantics online, I have tangentially found an overwhelming opinion that in modern C++ I should probably not be using raw pointers. If this is the case, then I would like to modify my code to use vectors for good coding practice.

  1. I mostly have read vectors over raw pointers. Is there any reason not to change to vectors?
  2. If I change the data to be stored in vectors instead of new[] arrays, do I still need to manually write copy/move assignment and constructor operators for classes? Are there any important differences between vector and new[] move/copy operators?
  3. Suppose I have a class called Levels, which contains several sparse matrix variables. I would like a function to create a vector of Levels, and return it:
vector<Levels> GetGridLevels(int &n, ... ) { 
    vector<Levels> grids(n);       
    \\ ... Define matrix variables for each Level object in grids ...
    return grids;
}

Will move semantics prevent this from being an expensive copy? I would think so, but it's a vector of objects containing objects containing member vector variables, which seems like a lot...


Solution

  • Yes, use std::vector<T> instead of raw T *.

    Also yes, the compiler will generate copy and move assignment operators for you and those will very likely have optimal performance, so don't write your own. If you want to be explicit, you can say that you want the generated defaults:

    struct S
    {
      std::vector<int> numbers {};
    
      // I want a default copy constructor
      S(const S&) = default;
    
      // I want a default move constructor
      S(S &&) noexcept = default;
    
      // I want a default copy-assignment operator
      S& operator=(const S&) = default;
    
      // I want a default move-assignment operator
      S& operator=(S&&) noexcept = default;
    };
    

    Regarding your last question, if I understand correctly, you mean whether returning a move-aware type by-value will be efficient. Yes, it will. To get the most out of your compiler's optimizations, follow these rules:

    • Return by-value (not by const value, this will inhibit moving).

    • Don't return std::move(x), just return x (at least if your return type is decltype(x)) so not to inhibit copy elision.

    • If you have more than one return statement, return the same object on every path to facilitate named return value optimization (NRVO).

      std::string
      good(const int a)
      {
        std::string answer {};
        if (a % 7 > 3)
          answer = "The argument modulo seven is greater than three.";
        else
          answer = "The argument modulo seven is less than or equal to three.";
        return answer;
      }
      
      std::string
      not_so_good(const int a)
      {
        std::string answer {"The argument modulo seven is less than or equal to three."};
        if (a % 7 > 3)
          return "The argument modulo seven is greater than three.";
        return answer;
      }
      
    • For those types where you write move constructors and assignment operators, make sure to declare them noexcept or some standard library containers (notably std::vector) will refuse to use them.