一个RVO引起的错误

之前在写一个list加法时(类似eopl Ex2.1的C++实现),在Visual Studio 2017中运行,发现Debug模式下所有结果都是正确的,而Release模式下所有的都错了。仔细检查了好几遍代码都没有发现错误,后来换g++ -S查看汇编才发现,默认的参数编译的结果中没有调用复制构造函数,于是检查代码发现完美符合NRVO的条件,所以被优化掉了,然后,加上-fno-elide-constructors在跑一遍,结果都正常了。

下面是一个最小化的重现代码:

#include <iostream>

class List
{
  private:
    struct Node
    {
      int data;
      Node* next;
    };

    Node* root_;

  public:
    List() : root_{nullptr} {}

    List(std::initializer_list<int> l) : List{}
    {
      for(auto x: l)
      {
        this->push(x);
      }
    }

    List(const List& rhs) : List{}
    {
      Node* r = rhs.root_;
      while(r)
      {
        this->push(r->data);
        r = r->next;
      }
    }

    ~List()
    {
      this->clear();
    }

    void push(int x)
    {
      Node* n = new Node;
      n->data = x;
      n->next = root_;
      root_ = n;
    }

    List& reverse()
    {
      Node* prev = nullptr;
      Node* next = nullptr;
      while(root_)
      {
        next = root_->next;
        root_->next = prev;
        prev = root_;
        root_ = next;
      }
      root_ = prev;
      return *this;
    }

    void clear()
    {
      Node* tmp = root_;
      while(tmp)
      {
        root_ = tmp->next;
        delete tmp;
        tmp = root_;
      }
    }

    List& show()
    {
      Node* tmp = root_;
      while(tmp)
      {
        printf("%d ", tmp->data);
        tmp = tmp->next;
      }
      putchar('\n');
      return *this;
    }
};

List copy_list(List& l)
{
  return l;
}

int main()
{
  List l{1, 2, 3};
  l.show();
  auto r = copy_list(l);
  r.show();
}

结果如下: result

有一点要注意的是,这里不能用-std=c++17,因为C++17增加了Guaranteed Copy Elision。

为了使以上代码不受RVO的影响,只能将push方法改为非头插的,比如:

List& push(int x)
{
  Node* n = new Node(x);
  if(root_)
  {
    tail_->next = n;
    tail_ = n;
  }
  else
  {
    root_ = n;
    root_->next = nullptr;
    tail_ = root_;
  }
  return *this;
}

所以,如果所有的代码都用C++17的话,一开始就会发现结果不对了(即使不知道RVO)。。。