나는 당신이 올바른 관찰이지만 잘못된 해석을 가지고 있다고 믿습니다!
이 경우 모든 일반 영리한 컴파일러는 (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 부를 얻습니다! 조금 신비한 것처럼 들리지만 그 이유는 초기화 목록의 구현 때문입니다! 목록의 반복자는 목록 const T*
에서 요소를 이동할 수 없으므로 목록 에서 이동할 수 없습니다. 이 주제에 대한 자세한 답변은 initializer_list 및 move semantics 에서 찾을 수 있습니다.
예 2)이 경우 초기 시공과 4 개의 값을 얻습니다. 그것은 특별한 것이 아니며 우리가 기대할 수있는 것입니다.
예 3) 또한 여기서 우리는 시공과 일부 움직임이 예상대로 진행됩니다. 내 stl 구현으로 벡터는 매번 요소 2 씩 증가합니다. 그래서 우리는 첫 번째 구조, 또 다른 구조를 보았습니다. 벡터의 크기가 1에서 2로 조정되기 때문에 첫 번째 요소의 이동을 봅니다. 3을 추가하는 동안 처음 두 요소의 이동이 필요한 2에서 4로 크기가 조정됩니다. 예상대로!
예 4) 이제 공간을 확보하고 나중에 채 웁니다. 이제 우리는 더 이상 사본과 움직임이 없습니다!
모든 경우에 벡터를 호출자에게 돌려 보냄으로써 어떠한 이동이나 복사도 볼 수 없습니다! (N) RVO가 진행 중이며이 단계에서 추가 조치가 필요하지 않습니다!
질문으로 돌아 가기 :
"C ++ 가짜 복사 작업을 찾는 방법"
위에서 보았 듯이 디버깅 목적으로 프록시 클래스를 도입 할 수 있습니다.
copy-ctor를 비공개로 설정하면 원하는 복사본과 숨겨진 복사본이있을 수 있으므로 대부분의 경우 작동하지 않을 수 있습니다. 위와 같이 예를 들어 4와 같은 코드 만 개인용 copy-ctor와 함께 작동합니다! 우리가 평화로 평화를 채울 때 예제 4가 가장 빠르면 질문에 대답 할 수 없습니다.
여기서 "원치 않는"사본을 찾는 일반적인 솔루션을 제공 할 수 없습니다. 의 호출을 위해 코드를 발굴하더라도 최적화 memcpy
되지 않은 것을 찾을 수 없으며 memcpy
라이브러리 memcpy
함수를 호출하지 않고 작업을 수행하는 어셈블러 명령어를 직접 볼 수 있습니다.
내 힌트는 그런 사소한 문제에 초점을 맞추지 않는 것입니다. 실제 성능 문제가있는 경우 프로파일 러를 사용하여 측정하십시오. 잠재적 인 성능 저하 요인이 너무 많기 때문에 허위 memcpy
사용법 에 많은 시간을 투자하는 것은 그리 가치있는 생각이 아닙니다.
std::vector
어떤 방법 으로든 복사 하는 것이 의도 한 것이 아니라는 것에 동의하지 않습니다 . 귀하의 예는 명시 적 사본을 보여 주며std::move
, 사본이 원하는 것이 아니라면 자신이 제안한대로 기능 을 적용하는 것은 자연스럽고 올바른 접근 방식 (다시 말해서) 입니다. 최적화 플래그가 켜져 있고 벡터가 변경되지 않으면 일부 컴파일러는 복사를 생략 할 수 있습니다.