2013-10-10 3 views
1

std :: vector 클래스에 요소를 삽입하는 데 생성자/할당 연산자를 사용하는 방법은 어떻게 제어됩니까? 난 내가 컴파일 할 때std :: vector로 시맨틱을 이동 및 복사하십시오.

#include<iostream> 
#include<vector> 
using namespace std; 

class copyer{ 
    double d; 
public: 
    //ban moving 
    copyer(copyer&& c) = delete; 
    copyer& operator=(copyer&& c) = delete; 
    //copy construction 
    copyer(const copyer& c){ 
     cout << "Copy constructor!" << endl; 
     d = c.d; 
    } 
    copyer& copy(const copyer& c){ 
     cout << "Copy constructor!" << endl; 
     d = c.d; 
     return *this; 
    } 
    //Constructor 
    copyer(double s) : d(s) { } 
    double fn(){return d;} 
}; 

class mover{ 
    double d; 
public: 
    //ban copying 
    mover(const mover& c) = delete; 
    mover& operator=(const mover& c) = delete; 
    //move construction 
    mover(mover&& c){ 
     cout << "Move constructor!" << endl; 
     d = c.d; 
    } 
    mover& copy(mover&& c){ 
     cout << "Move constructor!" << endl; 
     d = c.d; 
     return *this; 
    } 
    //Constructor 
    mover(double s) : d(s) { } 
    double fn(){return d;} 
}; 

template<class P> class ConstrTests{ 
    double d; 
    size_t N; 
    std::vector<P> object; 
public: 
    ConstrTests(double s, size_t n) : d(s) , N(n) { 
     object.reserve(N); 
     for(int i = 0; i<N; i++){ 
      object.push_back(P((double) i*d)); 
     } 
    } 
    void test(){ 
     int i = 0; 
     while(i<N){ 
      cout << "Testing " <<i+1 << "th object: " << object.at(i).fn(); 
      i++; 
     } 
    } 
}; 

을 다음과 같이 사용하지 않도록 내가 아무런 문제가

size_t N = 10; 
double d = 4.0; 
ConstrTests<mover> Test1 = ConstrTests<mover>(d,N); 
Test1.test(); 

을 실행하고 싶었다 생성자/지정을 보내고 delete하여하려고 노력하지만, 경우에 대신 내가

시도
size_t N = 10; 
double d = 4.0; 
ConstrTests<copyer> Test1 = ConstrTests<copyer>(d,N); 
Test1.test(); 

은 내가 삭제 move 생성자를 사용하려고 해요라는 컴파일에서 오류가 발생합니다. 즉 당신이 copyer

//ban moving 
copyer(copyer&& c) = delete; 
copyer& operator=(copyer&& c) = delete; 

에서이 줄을 제거하면 다음 코드가 잘 컴파일하고 예상대로 작동

답변

4

, std::vector<copyer>는 복사 생성자를 사용하여 std::vector<mover>는 이동-생성자를 사용합니다. 당신은 copyer의 이동 생성자를 선언하고 삭제로 정의했다

. 즉, 과부하 해결에 copyer::copyer(copyer&& c)이 참여하지만 선택하면 코드가 잘못 작성되었음을 의미합니다. 전화 object.push_back(P((double) i*d));이 호출을 트리거합니다.

왜 위의 줄을 제거하면 문제가 해결 되었습니까? 기존 C++ 98

우리는 컴파일러가 선언하는 복사 생성자를 선언하고 우리에게 하나를 구현하지 않는 경우. C++ 11에서는이 규칙이 약간 변경되었습니다. 이동 생성자가 선언 된 경우 컴파일러는 복사 생성자를 암시 적으로 정의하지 않습니다. (이 주제에 대해서는 더 많은 내용이 있지만이 부분만으로도 충분합니다.) 마찬가지로, 이동 생성자를 선언하지 않으면 컴파일러가 (예를 들어) 복사 생성자를 선언하지 않으면 컴파일러에서 암시 적으로 정의합니다. 위의 라인을 제거 한 후

지금, copyer는 사용자 선언 복사 생성자가 입주 생성자를하지 구매해야합니다. 그렇다면, 당신이나 컴파일러 모두 이동 생성자를 선언하지 않았을 것입니다. 이 경우 object.push_back(P((double) i*d));은 복사 생성자에 대한 호출을 트리거합니다. 우리가 C++ 98 호환 컴파일러로 컴파일했다면 이런 일이 일어 났을 것입니다. 이 경우 하위 호환성이 유지되고 이전 코드가 손상되지 않습니다.

1

호출되는 버전은 과부하 해결로 결정됩니다. prvalue 또는 xvalue를 사용하여 push_back을 호출하면 이동 생성자가 noexcept라고 가정하고 이동 생성자를 사용합니다. 그렇지 않으면 복사 생성자를 계속 사용하고 복사 생성자가 삭제되면 강제 실행됩니다 던지는 이동 생성자를 사용하는 벡터.이 경우 벡터는 더 이상 강력한 예외 보장을 제공 할 수 없습니다. (즉, 이동 생성자에서 예외가 발생하면 벡터의 내용이 손상된 것입니다.)

일반적으로 push_back을 수행 할 때마다 재 할당이 필요할 수 있습니다.이 경우 이동 또는 복사 생성자를 사용하여 모든 요소를 ​​새 메모리 블록으로 전송해야합니다. 이동 생성자를 사용하면 일반적으로 좋은 결과를 얻을 수 있습니다. 그럴 경우 어떤 것도 던지지 않을 것입니다.이 경우 원래 벡터에서 일부 객체를 이미 이동 시켰을 수 있습니다. 그리고 그것들을 뒤로 움직이는 것도 그것이 던질 수있는 대안이 아닙니다. 이런 이유로 강한 예외 보장이 완화됩니다.

따라서 일반적으로 이동 생성자 noexcept를 복사 생성자를 통해 호출하려는 경우이를 선언하십시오. (위의 예제에서 복사 생성자가 throw되면 새 메모리를 할당 해제하고 예외를 다시 throw 할 수 있으며 vector는 여전히 push_back 호출 이전과 동일합니다.

따라서 일반적으로 사용할 수있는 생성자와 인수 유형 (lvalue와 prvalue/xvalue)에 따라 호출 할 대상을 제어합니다. 생성자가 있다고 가정하면 push_back을 호출하여 std :: move를 사용하여이를 제어 할 수 있습니다. 가능한 경우 인수를 xvalue로 효과적으로 변환합니다.

참고; 내가 push_back에 대해 말한 것은 일반적으로 삽입에도 적용됩니다. Note2 : push_back이 호출 될 때마다 예외가 발생하면 호출이 아무런 영향을 미치지 않을 것이라는 보장이 있습니다. 이것은 강력한 예외 보장으로 알려져 있으며 제공 할 수 없습니다. 만약 당신이 가진 모든 것은 던지기 이동 생성자입니다.

관련 문제