一个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();
}
结果如下:
有一点要注意的是,这里不能用-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)。。。