답변:
기술 개요- 이 답변으로 건너 뛰십시오 .
복사 제거가 발생하는 일반적인 경우- 이 답변으로 건너 뛰십시오 .
복사 제거는 특정 상황에서 추가 (잠재적으로 비싼) 복사를 방지하기 위해 대부분의 컴파일러가 구현하는 최적화입니다. 실제로는 가치에 의한 반품 또는 가치에 의한 반품이 가능합니다 (제한 사항이 적용됨).
객체 복사 / 이동에 부작용이 있더라도 as-if 규칙 복사 제거를 적용 할 수 있는 유일한 최적화 형식입니다 (ha!) .
다음 예제는 Wikipedia 에서 가져온 것입니다 .
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C();
}
int main() {
std::cout << "Hello World!\n";
C obj = f();
}
컴파일러 및 설정에 따라 다음 출력 이 모두 유효합니다 .
안녕하세요 세계!
사본이 만들어졌습니다.
사본이 만들어졌습니다.
안녕하세요 세계!
사본이 만들어졌습니다.
안녕하세요 세계!
이것은 또한 더 적은 수의 객체를 생성 할 수 있다는 것을 의미하므로, 호출되는 특정 소멸자 수에 의존 할 수도 없습니다. 복사 / 이동 생성자 또는 소멸자 내부에는 호출 할 수 없으므로 중요한 논리가 없어야합니다.
복사 또는 이동 생성자에 대한 호출이 생략 된 경우 해당 생성자가 여전히 존재하고 액세스 가능해야합니다. 이렇게하면 복사 제거가 일반적으로 복사 할 수없는 개체 (예 : 개인 또는 삭제 된 복사 / 이동 생성자가 있기 때문에)를 복사 할 수 없도록합니다.
C ++ 17 : C ++ 17부터 객체가 직접 반환되면 Copy Elision이 보장됩니다.
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C(); //Definitely performs copy elision
}
C g() {
C c;
return c; //Maybe performs copy elision
}
int main() {
std::cout << "Hello World!\n";
C obj = f(); //Copy constructor isn't called
}
기술적 인 견해와 소개가 필요하지 않으면 이 답변으로 건너 뛰십시오 .
복사 제거가 발생하는 일반적인 경우- 이 답변으로 건너 뛰십시오 .
복사 제거 는 다음 표준에 정의되어 있습니다.
같이
31) 특정 기준이 충족되면 객체의 복사 / 이동 생성자 및 / 또는 소멸자가 부작용이 있더라도 구현시 클래스 객체의 복사 / 이동 구성을 생략 할 수 있습니다. 이러한 경우, 구현에서는 생략 된 복사 / 이동 조작의 소스 및 대상을 동일한 오브젝트를 참조하는 두 가지 다른 방법으로 취급하며, 해당 오브젝트의 파괴는 두 오브젝트가 있었을 때 후기에 발생합니다. 최적화없이 파괴되었습니다. 123 라는 복사 / 이동 작업이 생략, 복사 생략이 (여러 복사본을 제거하기 위해 결합 될 수있다) 다음과 같은 경우에 허용된다 :
— 클래스 반환 유형이있는 함수의 return 문에서 표현식이 함수 반환 유형과 동일한 cvunqualified 유형을 가진 비 휘발성 자동 객체 (함수 또는 catch-clause 매개 변수 제외)의 이름 인 경우 자동 객체를 함수의 반환 값으로 직접 구성하여 복사 / 이동 작업을 생략 할 수 있습니다.
— throw-expression에서 피연산자가 범위가 가장 안쪽에있는 try-block의 끝을 넘어 확장되지 않는 비 휘발성 자동 객체 (함수 또는 catch-clause 매개 변수 제외)의 이름 인 경우 (있는 경우) 1) 피연산자에서 예외 객체 (15.1) 로의 복사 / 이동 작업은 자동 객체를 예외 객체로 직접 구성하여 생략 할 수 있습니다.
— 참조 (12.2)에 바인딩되지 않은 임시 클래스 객체를 동일한 cv-unqualified 유형의 클래스 객체로 복사 / 이동할 때 임시 객체를 객체에 직접 구성하여 복사 / 이동 작업을 생략 할 수 있습니다. 생략 된 복사 / 이동의 대상
— 예외 처리기의 예외 선언 (Clause 15)이 예외 개체 (15.1)와 동일한 유형 (cv-qualification 제외)의 개체를 선언하면 예외 선언을 처리하여 복사 / 이동 작업을 생략 할 수 있습니다. 예외 선언에 의해 선언 된 객체에 대한 생성자와 소멸자의 실행을 제외하고 프로그램의 의미가 변경되지 않으면 예외 객체의 별명으로 사용됩니다.
123) 두 개가 아닌 하나의 개체 만 파괴되고 하나의 복사 / 이동 생성자가 실행되지 않기 때문에 생성 된 개체마다 여전히 하나의 개체가 파괴됩니다.
주어진 예는 다음과 같습니다.
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
설명했다 :
여기서 제거 기준을 결합하여 클래스의 복사 생성자에 대한 두 가지 호출을 제거 할 수 있습니다
Thing
.t
함수의 반환 값을 위해 로컬 자동 개체 를f()
임시 개체로 복사하고 해당 임시 개체를 개체로 복사합니다t2
. 효과적으로 로컬 객체의 구성은t
전역 객체를 직접 초기화하는 것으로 볼 수 있으며t2
해당 객체의 파괴는 프로그램 종료시 발생합니다. Thing에 이동 생성자를 추가해도 동일한 효과가 있지만 임시 객체에서 제거 된 이동 구성입니다t2
.
기술 개요- 이 답변으로 건너 뛰십시오 .
기술적 인 견해와 소개가 필요하지 않으면 이 답변으로 건너 뛰십시오 .
(명명 된) 반환 값 최적화는 일반적인 형태의 복사 제거입니다. 메소드에서 값으로 리턴 된 오브젝트에 사본이 생략 된 상황을 나타냅니다. 표준에 제시된 예제 는 오브젝트의 이름이 지정되므로 이름 지정된 리턴 값 최적화를 보여줍니다 .
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
임시가 리턴 될 때 정기적 인 리턴 값 최적화 가 발생합니다.
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
return Thing();
}
Thing t2 = f();
복사 제거가 발생하는 다른 일반적인 장소는 임시 값이 값으로 전달되는 경우입니다 .
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
void foo(Thing t);
foo(Thing());
또는 예외가 발생하여 값으로 잡힐 때 :
struct Thing{
Thing();
Thing(const Thing&);
};
void foo() {
Thing c;
throw c;
}
int main() {
try {
foo();
}
catch(Thing c) {
}
}
대부분의 상업용 컴파일러는 복사 제거 및 (N) RVO (최적화 설정에 따라 다름)를 지원합니다.
복사 제거는 불필요한 객체 복사 / 이동을 제거하는 컴파일러 최적화 기술입니다.
다음과 같은 상황에서 컴파일러는 복사 / 이동 작업을 생략 할 수 있으므로 연관된 생성자를 호출하지 않아도됩니다.
#include <iostream>
using namespace std;
class ABC
{
public:
const char *a;
ABC()
{ cout<<"Constructor"<<endl; }
ABC(const char *ptr)
{ cout<<"Constructor"<<endl; }
ABC(ABC &obj)
{ cout<<"copy constructor"<<endl;}
ABC(ABC&& obj)
{ cout<<"Move constructor"<<endl; }
~ABC()
{ cout<<"Destructor"<<endl; }
};
ABC fun123()
{ ABC obj; return obj; }
ABC xyz123()
{ return ABC(); }
int main()
{
ABC abc;
ABC obj1(fun123());//NRVO
ABC obj2(xyz123());//NRVO
ABC xyz = "Stack Overflow";//RVO
return 0;
}
**Output without -fno-elide-constructors**
root@ajay-PC:/home/ajay/c++# ./a.out
Constructor
Constructor
Constructor
Constructor
Destructor
Destructor
Destructor
Destructor
**Output with -fno-elide-constructors**
root@ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors
root@ajay-PC:/home/ajay/c++# ./a.out
Constructor
Constructor
Move constructor
Destructor
Move constructor
Destructor
Constructor
Move constructor
Destructor
Move constructor
Destructor
Constructor
Move constructor
Destructor
Destructor
Destructor
Destructor
Destructor
복사 제거가 발생하고 복사 / 이동 생성자가 호출되지 않더라도 존재하고 액세스 할 수 있어야합니다 (최적화가 전혀 발생하지 않은 것처럼) 그렇지 않으면 프로그램이 잘못 구성됩니다.
소프트웨어의 관찰 가능한 행동에 영향을 미치지 않는 장소에서만 그러한 복사 제거를 허용해야합니다. 복사 제거는 관찰 가능한 부작용을 갖도록 허용 된 유일한 최적화 형태입니다. 예:
#include <iostream>
int n = 0;
class ABC
{ public:
ABC(int) {}
ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect
}; // it modifies an object with static storage duration
int main()
{
ABC c1(21); // direct-initialization, calls C::C(42)
ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )
std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
return 0;
}
Output without -fno-elide-constructors
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp
root@ajay-PC:/home/ayadav# ./a.out
0
Output with -fno-elide-constructors
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors
root@ajay-PC:/home/ayadav# ./a.out
1
GCC는 -fno-elide-constructors
복사 제거를 비활성화 하는 옵션을 제공합니다 . 가능한 복사 제거를 피하려면을 사용하십시오 -fno-elide-constructors
.
이제 거의 모든 컴파일러는 최적화가 활성화 된 경우 (및 다른 옵션이 비활성화되지 않은 경우) 복사 제거를 제공합니다.
각각의 카피 제거시, 카피의 하나의 구성 및 하나의 매칭 파괴가 생략되어, CPU 시간을 절약하고, 하나의 객체가 생성되지 않아서, 스택 프레임상의 공간을 절약한다.
ABC obj2(xyz123());
은 NRVO 또는 망막 정맥 폐쇄입니까? 다음과 같은 임시 변수 / 객체를 얻지 못함 ABC xyz = "Stack Overflow";//RVO