相信您的观察正确,但解释错误!
返回值将不会发生复制,因为在这种情况下,每个普通的聪明编译器都会使用(N)RVO。从C ++ 17开始,这是强制性的,因此通过从函数返回本地生成的向量,您将看不到任何副本。
好,让我们一起玩一下,std::vector
在构建过程中或逐步填充过程中会发生什么。
首先,让我们生成一个数据类型,使每个副本或移动都像这样显示:
template <typename DATA >
struct VisibleCopy
{
private:
DATA data;
public:
VisibleCopy( const DATA& data_ ): data{ data_ }
{
std::cout << "Construct " << data << std::endl;
}
VisibleCopy( const VisibleCopy& other ): data{ other.data }
{
std::cout << "Copy " << data << std::endl;
}
VisibleCopy( VisibleCopy&& other ) noexcept : data{ std::move(other.data) }
{
std::cout << "Move " << data << std::endl;
}
VisibleCopy& operator=( const VisibleCopy& other )
{
data = other.data;
std::cout << "copy assign " << data << std::endl;
}
VisibleCopy& operator=( VisibleCopy&& other ) noexcept
{
data = std::move( other.data );
std::cout << "move assign " << data << std::endl;
}
DATA Get() const { return data; }
};
现在开始一些实验:
using T = std::vector< VisibleCopy<int> >;
T Get1()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec{ 1,2,3,4 };
std::cout << "End init" << std::endl;
return vec;
}
T Get2()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec(4,0);
std::cout << "End init" << std::endl;
return vec;
}
T Get3()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
T Get4()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.reserve(4);
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
int main()
{
auto vec1 = Get1();
auto vec2 = Get2();
auto vec3 = Get3();
auto vec4 = Get4();
// All data as expected? Lets check:
for ( auto& el: vec1 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec2 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec3 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec4 ) { std::cout << el.Get() << std::endl; }
}
我们可以观察到:
示例1)我们从初始化列表创建一个向量,也许我们希望看到4次构造和4次移动。但是我们得到了4份!听起来有些神秘,但是原因是初始化列表的实现!完全不允许将其从列表中移出,因为列表中的迭代器是a const T*
,这使得无法从列表中移出元素。可以在以下位置找到关于此主题的详细答案:initializer_list和move语义
示例2)在这种情况下,我们得到一个初始构造和该值的4个副本。那没什么特别的,这是我们可以期待的。
例3)同样在这里,我们的构造和一些动作符合预期。通过我的stl实现,向量每次都会增长2倍。因此,我们看到了第一个构造,另一个构造,并且由于向量的大小从1调整为2,因此我们看到了第一个元素的移动。在添加3个元素的同时,我们将尺寸从2调整为4,这需要移动前两个元素。一切都如预期!
示例4)现在我们保留空间并在以后填充。现在我们没有副本,也没有动静!
在所有情况下,我们都不会看到任何移动,也根本不会通过将引导程序返回给调用方来进行复制!(N)RVO正在发生,在此步骤中无需采取进一步措施!
回到您的问题:
“如何查找C ++伪复制操作”
如上所示,您可以在两者之间引入代理类以进行调试。
在许多情况下,将复制ctor设为私有可能不起作用,因为您可能有一些想要的副本和一些隐藏的副本。如上所述,只有示例4的代码才能与私有copy-ctor一起使用!我不能回答这个问题,如果例子4是最快的例子,因为我们以和平填充了和平。
抱歉,我无法提供在此处查找“不需要的”副本的一般解决方案。即使您为的调用而挖掘代码memcpy
,也不会找到全部,因为它们memcpy
也会被优化,并且直接看到一些汇编程序指令可以完成工作,而无需调用您的库memcpy
函数。
我的提示不是着眼于这样一个小问题。如果您有实际的性能问题,请进行探查并进行测量。有太多潜在的性能杀手,以至于在虚假memcpy
使用上花很多时间似乎不是一个值得的主意。
std::vector
以任何方式进行的复制并不是它所声称的那样。您的示例显示了一个显式副本,这是自然而正确的方法(再次请恕我直言),std::move
如果您不是您想要的副本,则按照您自己的建议应用该功能。请注意,如果打开了优化标志并且向量不变,则某些编译器可能会省略复制。