2016-06-05 3 views
4

저는 std::bind과 함께 작업하고 있지만 멤버 클래스 함수와 함께 사용할 때 어떻게 작동하는지 아직 알 수 없습니다.std :: bind가 멤버 함수와 함께 작동하는 방법

우리는 다음과 같은 기능이있는 경우 :

double my_divide (double x, double y) {return x/y;} 

내가 완벽하게 잘 코드의 다음 줄 이해 : 지금

auto fn_half = std::bind (my_divide,_1,2);    // returns x/2 

std::cout << fn_half(10) << '\n';      // 5 

그러나, 다음 코드를 사용하여 우리는 멤버 함수에 바인드가 질문이 몇 개 있습니다.

struct Foo { 
    void print_sum(int n1, int n2) 
    { 
     std::cout << n1+n2 << '\n'; 
    } 
    int data = 10; 
}; 

Foo foo; 

auto f = std::bind(&Foo::print_sum, &foo, 95, _1); 
f(5); 
  • 이유는 첫 번째 인수는 참조 무엇입니까? 나는 이론적 인 설명을하고 싶다.

  • 두 번째 인수는 객체에 대한 참조이며 가장 복잡한 부분은 이해하기 쉽습니다. std::bind에 컨텍스트가 필요하다고 생각합니다. 맞습니까? 항상 이것처럼? std::bind 첫 번째 인수가 멤버 함수 일 때 참조가 필요한 일종의 구현이 있습니까? std::bind docs에서

+3

식별자 뒤에 '&'가 오는 것은 "참조"를 의미하지 않습니다. 변수를 선언하지 않는 한,'&'앞에는 typename이옵니다. 식별자에'&'가 적용되면 포인터 *가 생깁니다. –

+0

일반적으로'std :: bind'를 사용하지 마십시오. 그것은 당신이 기본을 마스터 한 후에도 놀라운 결과를 만들어내는 정말 깊은 단점이 있습니다. 나는 그럴 가치가 없다는 것을 안다. – Yakk

답변

11

당신이 확실하게 말을 의미하는 "첫번째 인수가 참조입니다"라고 "첫번째 인수가 포인터입니다 ": & 연산자는 객체의 주소를 가져와 포인터를 생성합니다.

이 질문에 대답하기 전에, 잠시 한 걸음 뒤로 물러나 당신이 기능을 제공

std::bind(my_divide, 2, 2) 

를 사용할 때 std::bind()의 첫 번째 사용 살펴 보자. 어떤 함수가 전달되면 포인터로 쇠퇴합니다.상기 식 명시 std::bind()의 첫번째 인자는 함수를 호출하는 방법을 식별 목적 어드레스

std::bind(&my_divide, 2, 2) 

복용 이것과 동등하다. 위의 경우에는 double(*)(double, double) 유형의 함수에 대한 포인터입니다. 적절한 함수 호출 연산자를 사용하는 다른 호출 가능 객체도 마찬가지입니다.

구성원 함수는 매우 일반적이므로 std::bind()은 멤버 함수에 대한 포인터를 처리 할 수 ​​있도록 지원합니다. &print_sum을 사용하면 멤버 함수에 대한 포인터 즉, void (Foo::*)(int, int) 유형의 엔티티를 얻을 수 있습니다. 함수 이름은 암시 적으로 함수에 대한 포인터로 붕괴되지만 즉, &은 생략 될 수 있지만 멤버 함수 (또는 데이터 멤버)에 대해서는 동일하지 않습니다. 멤버 함수에 대한 포인터를 얻으려면 함수를 사용할 필요가 있습니다. &.

구성원에 대한 포인터는 class에만 해당하지만 클래스의 모든 개체와 함께 사용할 수 있습니다. 즉, 특정 개체와 독립적입니다. C++은 멤버 함수를 직접적으로 객체에 바인딩하는 직접적인 방법이 없습니다 (C#에서는 적용된 멤버 이름을 가진 객체를 사용하여 객체에 직접 바인딩 된 함수를 얻을 수 있다고 생각하지만 이후 10 년 이상이 걸립니다). 나는 마지막으로 C# 비트를 프로그래밍했다.)

내부적으로, std::bind()은 멤버 함수에 대한 포인터가 전달되었음을 감지하고이를 호출 가능 객체 (예 : std::mem_fn())의 첫 번째 인수와 함께 호출 할 가능성이 높습니다. non-static 멤버 함수는 객체를 필요로하기 때문에, 호출 가능한 호출 가능한 객체의 첫 번째 인수는 해당 클래스의 객체에 대한 참조 또는 [스마트] 포인터입니다.

멤버 함수에 대한 포인터를 사용하려면 객체가 필요합니다. std::bind()이있는 멤버에 대한 포인터를 사용하는 경우 std::bind()의 두 번째 인수는 객체가 언제오고 있는지 지정해야합니다. 하여 예

std::bind(&Foo::print_sum, &foo, 95, _1) 

에서 생성 된 호출 객체 사용 &foo, 즉 대상이 foo (타입 Foo*)에 대한 포인터. std::bind()은 포인터처럼 보이는 것을 사용하기에 충분히 똑똑하고, 적절한 유형의 참조 (예 : std::reference_wrapper<Foo>)로 변환 가능한 것 또는 첫 번째 인수가 멤버에 대한 포인터 인 경우 객체의 [copy] 객체를 사용할 수 있습니다.

나는 당신이 회원에 대한 포인터를 본 적이 없다고 생각합니다. 그렇지 않으면 매우 명확합니다. apply() 단순히 두 Foo 객체에 대한 포인터와 멤버 함수에 대한 포인터를 가져옵니다

#include <iostream> 

struct Foo { 
    int value; 
    void f() { std::cout << "f(" << this->value << ")\n"; } 
    void g() { std::cout << "g(" << this->value << ")\n"; } 
}; 

void apply(Foo* foo1, Foo* foo2, void (Foo::*fun)()) { 
    (foo1->*fun)(); // call fun on the object foo1 
    (foo2->*fun)(); // call fun on the object foo2 
} 

int main() { 
    Foo foo1{1}; 
    Foo foo2{2}; 

    apply(&foo1, &foo2, &Foo::f); 
    apply(&foo1, &foo2, &Foo::g); 
} 

기능 : 다음은 간단한 예입니다. 각 객체가 가리키는 멤버 함수를 호출합니다. 이 재미있는 ->* 연산자는 개체에 대한 포인터에 멤버에 대한 포인터를 적용하고 있습니다. 또한 멤버에 대한 포인터를 객체에 적용하는 .* 연산자가 있습니다 (또는 객체와 마찬가지로 동작하므로 객체에 대한 참조). 멤버 함수에 대한 포인터는 객체를 필요로하기 때문에, 객체를 요청하는이 연산자를 사용할 필요가 있습니다. 내부적으로는 std::bind()이 발생합니다.

두 개의 포인터와 함께 apply()이 호출되고 &Foo::f이 호출되면 해당 개체에 f() 구성원이 호출 될 때와 완전히 동일하게 작동합니다.마찬가지로 두 포인터를 사용하여 apply()을 호출하고 &Foo::g을 호출하면 해당 객체에서 g()이라는 멤버가 호출 될 때와 완전히 동일하게 작동합니다 (의미 론적 동작은 동일하지만 컴파일러에서 함수를 인라인하는 데 더 많은 시간이 소요될 수 있으며 일반적으로 실패합니다). 회원에 대한 포인터가 관련되어있을 때 그렇게 함).

+0

반쯤 읽으면 나는 당혹 스러웠다. 누가 그토록 훌륭한 설명을 썼을 까? 나는 짐작할 수 있었다! – SergeyA

+1

놀라운 대답, Dietmar. ** (1) ** 함수 이름은 암시 적으로 함수에 대한 포인터로 쇠퇴하는 반면, 멤버 함수에 대한 포인터는 얻지 못합니다. 멤버 함수에 대한 포인터를 얻으려면 &를 사용하는 것이 필요합니다. ** (2) ** std :: bind()는 멤버 함수에 대한 포인터가 전달되어 호출 가능한 객체로 변환 될 가능성이 높습니다. ** (3) ** 멤버 함수에 대한 포인터를 사용하려면 객체 필요합니다. std :: bind()를 사용하여 멤버에 대한 포인터를 사용하는 경우 std :: bind()의 두 번째 인수는 객체가 언제 오는지 지정해야합니다. 고마워요! – Tarod

4

:

f는 멤버 함수에 대한 포인터입니다 귀하의 경우, Callable입니다

bind(F&& f, Args&&... args);. 포인터 이런 종류의 일반적인 기능에 대한 포인터에 비해 특별한 구문이

typedef void (Foo::*FooMemberPtr)(int, int); 

// obtain the pointer to a member function 
FooMemberPtr a = &Foo::print_sum; //instead of just a = my_divide 

// use it 
(foo.*a)(1, 2) //instead of a(1, 2) 

std::bind (그리고 std::invokein general)는 균일 한 방식으로 모든 경우를 포함한다. 예를 들어 당신이 가지고처럼 f 경우 포인터Foo 다음 Foo, 바인딩 설치된 제 1 Arg의 포인터 - 투 - 회원 (bind(&Foo::print_sum, foo, ...)도 작동하지만 foo가 복사) Foo의 인스턴스가 될 것으로 예상된다 또는 .

여기에는 pointers to members에 대한 추가 정보가 나와 있으며 12에는 바인드가 예상하는 것과 저장 함수를 호출하는 방법에 대한 전체 정보가 나와 있습니다.

당신은 또한 더 명확 할 수있는, 람다 대신 std::bind 사용할 수 있습니다

auto f = [&](int n) { return foo.print_sum(95, n); } 
+0

이상하게도'& Foo :: print_sum'은 호출 가능하지 않습니다! member에 대한 포인터, 멤버 함수에 대한 포인터조차도 함수 호출 연산자가 없습니다! 'std :: mem_fn (& Foo :: print_sum)'은 호출 가능합니다. 그러나'std :: bind()'는 첫 번째 인자로 멤버에 대한 포인터를 가지고 호출된다는 것을 이해합니다. –

+0

@ DietmarKühl Callable은 C++ 17에서 약간 다른 것을 의미합니다 : http://en.cppreference.com/w/cpp/types/is_callable. 그리고'std :: mem_fn','std :: bind' 및'std :: invoke'의 모든 패밀리는 기본 호출 가능 유형에 멋진 인터페이스를 제공합니다. –

+0

그러나 이것은 용어에 관한 질문 일뿐입니다. –