2011-09-23 5 views
0

고려 다음 코드 조각 :를 rvalue 참조, 포인터, 복사 생성자

int three() { 
    return 3; 
} 

template <typename T> 
class Foo { 
private: 
    T* ptr; 

public: 
    void bar(T& t) { ptr = new T(t); } 
    void bar(const T& t) { ptr = new T(t); } 
    void bar(T&& t) { (*ptr) = t; } // <--- Unsafe! 
}; 

int main() { 
    Foo<int> foo; 

    int a = 3; 
    const int b = 3; 

    foo.bar(a); // <--- Calls Foo::bar(T& t) 
    foo.bar(b); // <--- Calls Foo::bar(const T& t) 
    foo.bar(three()); // <--- Calls Foo::bar(T&& t); Runs fine, but only if either of the other two are called first! 

    return 0; 
} 

내 질문에, 왜 세 번째 오버로드 Foo::bar(T&& t) 충돌 프로그램을 수행합니까? 정확히 여기서 무슨 일이 일어나고있는거야? 함수가 돌아온 후에 t 매개 변수가 파괴됩니까?

또한 템플릿 매개 변수 T이 매우 큰 복사 생성자가있는 매우 큰 개체라고 가정 해 봅니다. RValue References를 사용하여이 포인터에 직접 액세스하고 복사본을 만들지 않고 Foo::ptr에 할당 할 수 있습니까?

+0

작성한 코드로 인해 메모리 누수가 발생하지 않고 실제로 컴파일되지 않는 등의 문제가 발생하지 않습니다. 그것이 실제 사용하고있는 코드입니까? –

+0

잘 컴파일되고 ** 제대로 실행됩니다. http://ideone.com/Ypqxz – Nawaz

+0

이 정확한 코드는 Visual Studio 2010에서 잘 작동하는 것으로 보입니다 (메모리 누수가 있음에도 불구하고). rvalue 참조와 관련하여 컴파일러 버전이 호환되는지 확실합니까? – Chad

답변

3

. 이것은 정의되지 않은 동작입니다. 개체의 메모리를 만들어야하기 때문에 먼저 다른 두 가지 버전 중 하나를 호출해야합니다.
그래서 나는 ptr = new T(std::move(t));을 할 것입니다.
유형 T가 이동을 지원하면 이동 생성자가 호출됩니다.

업데이트

나는 그런 일을 제안합니다.

template <typename T> 
class Foo { 
private: 
    T obj; 

public: 
    void bar(T& t) { obj = t; } // assignment 
    void bar(const T& t) { obj = t; } // assignment 
    void bar(T&& t) { obj = std::move(t); } // move assign 
}; 

이것은 또한 당신의 접근 방식과 매우 쉽게 메모리 누수를 피할 것이다 : 당신이 foo는 내 포인터 유형을해야하는 경우 확실하지.
당신이 정말로 당신의 클래스 foo에의 포인터가 필요하면 어떻게 약 :

template <typename T> 
class Foo { 
private: 
    T* ptr; 

public: 
    Foo():ptr(nullptr){} 
    ~Foo(){delete ptr;} 
    void bar(T& t) { 
     if(ptr) 
      (*ptr) = t; 
     else 
      ptr = new T(t); 
    } 
    void bar(const T& t) { 
     if(ptr) 
      (*ptr) = t; 
     else 
      ptr = new T(t); 
    } 
    void bar(T&& t) { 
     if(ptr) 
      (*ptr) = std::move(t); 
     else 
      ptr = new T(std::move(t)); 
    } 
}; 
+0

오른쪽 및 다른 버전의 막대가 먼저 호출됩니다. –

+0

그래서'Foo'에 포인터를 초기화하여'new T()'로 포인터를 생성 한 다음'Foo :: bar()'에 move 생성자를 사용하면됩니까? 안전할까요? – Zeenobit

0

가정 당신 라는 다른 두 통화없이 foo.bar(three()); :

당신이이 일 거라고 생각 않은 이유는 무엇입니까? 코드는 다음과 본질적으로 동일하다 : 정의되지 않은 동작입니다

int * p; 
*p = 3; 

, p 유형 int의 유효한 변수를 가리키는되지 않기 때문에.

+0

p는 실제로 foobar (b)에 의해 생성 된 것을 가리 킵니다. 따라서 초기화되지 않았습니다 –

+0

@EmilioGaravaglia : 그래서 맨 위에 매우 굵은 선을 추가했습니다. –

+0

@Kerreck SB : 이런! 나는 그것을 보지 못했다! (내 눈은 이전 제목의 일부로 간주됩니다! 굵은 선으로 텍스트를 시작하지 마십시오. 다른 것과 속한 것처럼 보입니다.) –

0

해당 코드에서 오류가 발생하지 않아도됩니다. ptr은 이전 호출 인 bar에 의해 생성 된 기존 int 개체를 가리키며 세 번째 오버로드는 해당 개체에 새 값을 할당합니다.

그러나, 대신 이런 짓을하는 경우 : ptrint 객체에 대한 유효한 포인터하지 않기 때문에 foo.bar(three()); 라인 (예외를 의미하지는 않습니다) 정의되지 않은 동작을 할 것이다

int main() { 
    Foo<int> foo; 

    int a = 3; 
    const int b = 3; 
    foo.bar(three()); // <--- UB 

    return 0; 
} 

있다. 초기화되지 않은 포인터 역 참조이 줄
void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
당신이 할 수에서

+0

이 코드를 작성한 이래로 나는 당신이 의미하는 바를 깨닫습니다. 그리고 네 말이 맞아. 잘 돌아갑니다. 그러나 다른 오버로드 중 하나 후에'foo.bar (three())'가 호출되는 동안에 만 호출됩니다. – Zeenobit

0

은 "안전하지 않은"것, 여기에 새로운 객체를 ptr에에 할당하기 전에, 당신 ptr이 무엇의 운명에 대해 걱정해야한다는 것입니다 실제로 가리킨다.

foo.bar(three()); 

은 ptr이 실제로 뭔가를 가리 키기 전에 부여해야한다는 의미에서 안전하지 않습니다. 귀하의 경우가 더 적절한 코드

template<class T> 
class Foo 
{ 
    T* p; 
public: 
    Foo() :p() {} 
    ~Foo() { delete p; } 

    void bar(T& t) { delete p; ptr = new T(t); } 
    void bar(const T& t) { delete p; ptr = new T(t); } 
    void bar(T&& t) 
    { 
     if(!ptr) ptr = new T(std::move(t)); 
     else (*ptr) = std::move(t); 
    } 
} 

수 있습니다 foobar(a)

(A)에 의해 생성 된 하나를 잊고 새로운 객체를 가리 키도록 foo.bar(b);

그러나 foobar(b)ptr하게하여 만든 것을 가리키는;