2017-01-13 2 views
0

필자는 사용자에게 제공하는 C++ 프레임 워크를 가지고 있습니다.이 프레임 워크는 템플릿 구현 유형으로 자체 구현으로 작성한 템플릿 래퍼를 사용해야합니다. 래퍼는 RAII 클래스로 작동하며 사용자 클래스의 구현에 대한 포인터를 보유합니다. 사용자 코드를 깨끗하고 깔끔하게 만들기 위해서 (필자의 견해로는) 필자의 래퍼를 보유하고있는 포인터로 변환하는 캐스트 연산자를 제공한다. 이 방법 (다른 오버로드와 함께) 사용자는 내 래퍼를 포인터 인 것처럼 사용할 수 있습니다 (많은 경우 shared_ptr과 유사).C++ std :: 포인터 이동

사용자가 함수를 호출하는 곳을 발견하게되었습니다.이 래퍼에서 std :: move를 사용하여 구현 클래스에 대한 포인터를 사용합니다.

#include <iostream> 
using namespace std; 

struct my_interface { 
    virtual int bar() = 0; 
}; 

template <typename T> 
struct my_base : public my_interface { 
    int bar() { return 4; } 
}; 

struct my_impl : public my_base<int> {}; 

template <typename T> 
struct my_wrapper { 
    my_wrapper(T* t) { 
     m_ptr = t; 
    } 

    operator T*() { 
     return m_ptr; 
    } 

private: 
    T* m_ptr; 
}; 

void foo(my_interface* a) { 
    std::cout << a->bar() << std::endl; 
} 


int main() 
{ 
    my_impl* impl = new my_impl(); 
    my_wrapper<my_impl> wrapper(impl); 
    foo(std::move(wrapper)); 
    //foo(wrapper); 

    return 0; 
} 

[이것은 당연히 사건의 예에 불과하고, 래퍼 더 많은 방법이 있습니다,하지만 난 여기에 역할을하지 않는 확신 해요 : 여기 어떻게 생겼는지의 예 이 경우]

사용자는 래퍼에서 std :: move가 호출 된 다음 foo을 호출 한 후 래퍼가 비어 있거나 적어도 이동 된 것처럼 수정 될 것으로 예상합니다. , 실제로는 foo 전에 호출되는 유일한 메소드가 캐스트 연산자입니다.

foo에 대한 호출을 foo의 두 호출 사이에서 구별 할 수있는 방법이 있습니까? 즉 std::move을 사용하거나 사용하지 않고 전화 할 때입니까? 음매 오리의 의견에

편집 덕분에 나는 my_wrapper가 요구되는 통화 알고있는 방법을 발견,하지만 난이 갈 수있는 가장 좋은 방법입니다뿐만 아니라이에 대한 의견을 주셔서 감사합니다 정말 확실하지 않다 : 대신 이전 캐스트 연산자의

는 다음과 같은 두 가지 사용없이 호출 할 때 표준 : 이동 및 operator T*() &로 호출 할 때 지금 operator T*() &&를 호출

operator T*() & { 
    return m_ptr; 
} 

operator T*() &&{ 
    //Do something 
    return m_ptr; 
} 

가 호출됩니다.

+0

호출 사이트 또는 수신자가 구별 할 수 있습니까? –

+0

피 호출자는'my_impl' 타입을 의미합니까? – ZivS

+3

사용자의 기대치가 부당합니다. 무언가에'std :: move'를 호출하고 이동 된 객체를 소유하지 않는 함수로 전달하면, 객체가 비어 있거나 변경 될 것이라고 기대하는 것은 무리입니다. 예 :'std :: shared_ptr foo; ... std :: move (foo) -> bar();'bar가 소유하지 않기 때문에'foo'를 변경해서는 안됩니다. 'bar (std :: move (foo));'와 같은 경우. 'std :: move'는 객체를 움직이기 위해 호출 된 함수 * 권한 *을 주지만, 그것이 지정되지 않았다면 강제로 움직이지 않습니다. –

답변

3

사용자, 나는, 표준 : 이동이 래퍼에서 호출 된 경우, 다음 호출 후 래퍼가 비어 있습니다 foo는 (또는이 이동 된 것처럼 적어도 수정) 기대하는 것처럼

귀하의 기대가 잘못되었습니다. 이동이 발생하는 경우, 즉 일종의 자원의 소유권이 이전 된 경우에만 수정됩니다. 그러나 foo을 호출하면 래퍼 내부에있는 포인터에 액세스하기 때문에 아무 것도하지 않습니다. std::move을 호출하면 값을 변경하지 않는 rvalue에 인수를 전달하는 것 외에는 아무 것도하지 않습니다. 참조로 rvalue를 허용하는 일부 함수는이를 수정할 수 있으므로 std::move은이를 가능하게하지만 자체적으로 수행하지는 않습니다. rvalue를 이러한 함수에 전달하지 않으면 수정이 수행되지 않습니다.

당신이 정말로 당신이 그렇게 할 수있는 과부하 추가 할 수 있습니다 비울 수 있도록 할 경우

template<typename T> 
void foo(my_wrapper<T>&& w) { 
    foo(static_cast<my_interface*>(w)); 
    w = my_wrapper<T>{}; // leave it empty 
} 

하지만를 ... 왜? 왜 그렇게해야합니까?

당신이 할 경우, 래퍼 빈 남아 있지 :

my_wrapper<my_impl> w(new my_impl); 
my_wrapper<my_impl> w2 = std::move(w); 

을 그리고에 의해 비어되지 않습니다를 rvalue 래퍼를 복사하는 빈을 두지 않는 경우

my_wrapper<my_impl> w(new my_impl); 
my_wrapper<my_impl> w2; 
w2 = std::move(w); 

, 왜 회원에게 간단히 액세스하여 비워 두어야합니까? 그건 말이 안돼.

래퍼는 이동 생성자를 가지고 있으며, 예 떠날 할 수 있도록 할당 연산자를 이동하더라도 w 빈, 여전히를 rvalue 개체의 멤버에 액세스하는 객체를 수정해야한다는 것을 의미하지 않는다. operator T* 변환이 lvalue 또는 rvalue로 수행되는지 여부가 논리적으로 다른 이유는 무엇입니까? ?

(또한, 모두 포장 포인터 타입에서 에 암시 적 변환을 가지는 것은 좋은 생각 힌트 것을 정말 확실히 당신에게 있습니다 : 그건 좋은 생각 일반적으로 당신의 전환을 명시 적으로 만들기 위해 선호 특히. 동적으로 할당 된 객체에 대한 포인터를 다루는 경우)

+1

1 년 후 답을 읽는 것이 의미가 있습니다. 처음으로이 답을 읽는 것이 나에게 잘못되었다고 말할 수는 있습니다. 그러나 나는 그것이 완전히 풀리지 않았기 때문에 이제야 알았습니다. 이해 된 이동 의미론. 감사 :) – ZivS