将 class new[] 变量替换为向量 - 移动、复制运算符

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

我为我正在做的一些工作制作了一个稀疏矩阵 class。对于稀疏结构,我使用了指针,例如int* rowInd = new int[numNonZero]。对于 class,我编写了复制和移动赋值运算符,一切正常。

在网上阅读有关移动和复制语义的内容后,我突然发现了一种压倒性的观点,即在现代 C++ 中我可能不应该使用原始指针。如果是这种情况,那么我想修改我的代码以使用向量来进行良好的编码练习。

  1. 我主要通过原始指针读取向量。有什么理由不改成矢量吗?
  2. 如果我将数据更改为存储在向量中而不是新[] 数组中,我是否仍需要为classes 手动编写copy/move 赋值和构造函数运算符? vector 和 new[] move/copy 运算符之间有什么重要区别吗?
  3. 假设我有一个名为 Levels 的 class,它包含几个稀疏矩阵变量。我想要一个函数来创建一个级别向量,return 它:
vector<Levels> GetGridLevels(int &n, ... ) { 
    vector<Levels> grids(n);       
    \ ... Define matrix variables for each Level object in grids ...
    return grids;
}

移动语义会阻止它成为昂贵的副本吗?我会这么想的,但它是一个包含对象的对象向量,对象包含成员向量变量,这看起来很多...

  1. 与正确性无关。请注意,构造一个大小为 n 的向量意味着它将初始化其所有元素,因此您可能更愿意构造一个空向量,然后是 reserve(n),然后是 push_back 元素。
  2. 不,隐式移动 constructor/assignment 应该可以解决所有问题 - unless you suppress them.
  3. 是的,如果您不编写代码来阻止移动,您将自动从 std::vector 获得有效的移动。

此外,考虑使用现有的库,例如 Eigen,这样您就可以免费获得一些相当优化的例程。

是的,使用 std::vector<T> 而不是原始的 T *

还有,编译器会为您生成复制和移动赋值运算符,这些运算符很可能具有最佳性能,所以不要自己编写。如果你想明确一点,你可以说你想要生成的默认值:

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;
};

关于你的最后一个问题,如果我理解正确的话,你的意思是return按值return移动感知类型是否有效。是的,它会。要充分利用编译器的优化,请遵循以下规则:

  • Return 按值(不是按 const 值,这将禁止移动)。

  • 不要return std::move(x),只要return x(至少如果你的return类型是decltype(x))所以不要禁止复制省略。

  • 如果您有多个 return 语句,return 每个路径上的相同对象以促进命名 return 值优化 (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;
    }
    
  • 对于那些你编写移动构造函数和赋值运算符的类型,确保声明它们 noexcept 否则一些标准库容器(特别是 std::vector)将拒绝使用它们.

  1. 没有。在 99% 的情况下,std::vector 的最简单使用会比原始指针更好、更安全地完成工作,并且在您需要手动管理内存的不太常见的情况下,这些 class 可以与自定义一起使用allocators/deallocators(例如,如果您想要对齐内存以使用对齐的 SSE 内在函数)。如果您使用自定义分配器,代码可能会比原始指针更复杂,但更易于维护且不易出现内存问题。

  2. 根据您的其他成员是什么,以及您的 class 做什么,您可能需要实施 move/copy assignment/ctors。但这会简单得多。您可能必须自己实现它们,但对于您的向量,您只需要调用相应的 operators/ctors。代码将简单易读,并且您不会有段错误/内存泄漏的风险

  3. 是的,但移动语义甚至不是必需的。 Return值优化会负责优化的副本(实际上不会有副本)。然而,这是特定于编译器的,并且不受标准保证。