복사 제거 및 반환 값 최적화 란 무엇입니까? 란 무엇입니까? 그들은 무엇을 의미합니까? 어떤

복제 제거 란 무엇입니까? 반환 값 최적화 란 무엇입니까? 그들은 무엇을 의미합니까?

어떤 상황에서 발생할 수 있습니까? 한계는 무엇입니까?



답변

소개

기술 개요- 이 답변으로 건너 뛰십시오 .

복사 제거가 발생하는 일반적인 경우- 이 답변으로 건너 뛰십시오 .

복사 제거는 특정 상황에서 추가 (잠재적으로 비싼) 복사를 방지하기 위해 대부분의 컴파일러가 구현하는 최적화입니다. 실제로는 가치에 의한 반품 또는 가치에 의한 반품이 가능합니다 (제한 사항이 적용됨).

객체 복사 / 이동에 부작용이 있더라도 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
}

답변

표준 참조

기술적 인 견해와 소개가 필요하지 않으면 이 답변으로 건너 뛰십시오 .

복사 제거가 발생하는 일반적인 경우- 이 답변으로 건너 뛰십시오 .

복사 제거 는 다음 표준에 정의되어 있습니다.

12.8 클래스 객체 복사 및 이동 [class.copy]

같이

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 (최적화 설정에 따라 다름)를 지원합니다.


답변

복사 제거는 불필요한 객체 복사 / 이동을 제거하는 컴파일러 최적화 기술입니다.

다음과 같은 상황에서 컴파일러는 복사 / 이동 작업을 생략 할 수 있으므로 연관된 생성자를 호출하지 않아도됩니다.

  1. NRVO (Named Return Value Optimization) : 함수가 값으로 클래스 유형을 리턴하고 리턴 명령문의 표현식이 자동 저장 기간 (함수 매개 변수 아님)을 갖는 비 휘발성 오브젝트의 이름 인 경우 복사 / 이동 비 최적화 컴파일러에 의해 수행되는 것은 생략 될 수 있습니다. 그렇다면 반환 된 값은 함수의 반환 값이 이동되거나 복사 될 스토리지에 직접 구성됩니다.
  2. RVO (Return Value Optimization) : 함수가 순진한 컴파일러에 의해 목적지로 이동 또는 복사 될 이름없는 임시 오브젝트를 리턴하는 경우, 복사 또는 이동은 1에 따라 생략 될 수 있습니다.
#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 시간을 절약하고, 하나의 객체가 생성되지 않아서, 스택 프레임상의 공간을 절약한다.