あなたは正しい観察をしていると思いますが、間違った解釈をしています!
この場合、すべての通常の賢いコンパイラーは(N)RVOを使用するため、値が返されてもコピーは行われません。C ++ 17以降、これは必須であるため、関数からローカルで生成されたベクトルを返すことによってコピーを表示することはできません。
OK、少し遊んでみ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 semantics
例2)この場合、最初の構成と値の4つのコピーを取得します。それは特別なことではなく、私たちが期待できることです。
例3)ここでも、予想どおり建設と移動が行われます。私のstl実装では、ベクトルは毎回2倍に増加します。したがって、最初の構成と別の構成がわかります。ベクトルのサイズが1から2に変更されているため、最初の要素の動きがわかります。3つの要素を追加する際、最初の2つの要素の移動が必要な2から4へのサイズ変更を確認します。すべてが期待通りです!
例4)スペースを予約し、後で埋めます。これで、コピーも移動もなくなりました!
すべての場合において、ベクターを呼び出し元に戻すことによる移動やコピーはまったく見られません!(N)RVOが実行中であり、このステップでこれ以上のアクションは必要ありません。
あなたの質問に戻ります:
「C ++の疑似コピー操作を見つける方法」
上記のように、デバッグの目的でプロキシクラスを導入できます。
コピートラクターをプライベートにしても、必要なコピーと非表示のコピーが存在する可能性があるため、多くの場合機能しない可能性があります。上記のように、例4のコードのみがプライベートコピートラクターで動作します。そして、例4が最速の問題である場合、私たちは平和を平和で満たすため、質問に答えることはできません。
ここでは、「不要な」コピーを見つけるための一般的な解決策を提供できません。の呼び出しのためにコードを掘ったmemcpy
としてもmemcpy
、最適化されてすべてを見つけることはできず、ライブラリmemcpy
関数を呼び出さずにジョブを実行しているアセンブラー命令がいくつか直接表示されます。
私のヒントは、そのような小さな問題に焦点を当てることではありません。実際のパフォーマンスの問題がある場合は、プロファイラーを使用して測定します。潜在的なパフォーマンスキラーが非常に多いため、偽のmemcpy
使用に多くの時間を費やすことは、それほど価値のあるアイデアではないようです。
std::vector
、何らかの形でのコピーが意図したものではないことに同意しません。あなたの例は明示的なコピーを示しstd::move
ていますが、コピーが必要なものでない場合は、提案したとおりに関数を適用するのが自然で正しいアプローチです(これもimho)。一部のコンパイラでは、最適化フラグがオンになっていて、ベクターが変更されていない場合、コピーが省略されることがあります。