Sử dụng các đối tượng hàm std :: chung với các hàm thành viên trong một lớp


169

Đối với một lớp tôi muốn lưu trữ một số con trỏ hàm đến các hàm thành viên của cùng một lớp trong một đối tượng maplưu trữ std::function. Nhưng tôi thất bại ngay từ đầu với mã này:

class Foo {
    public:
        void doSomething() {}
        void bindFunction() {
            // ERROR
            std::function<void(void)> f = &Foo::doSomething;
        }
};

Tôi nhận được error C2064: term does not evaluate to a function taking 0 argumentstrong xxcallobjkết hợp với một số lỗi mẫu instantiation lạ. Hiện tại tôi đang làm việc trên Windows 8 với Visual Studio 2010/2011 và trên Win 7 với VS10 thì cũng thất bại. Lỗi phải dựa trên một số quy tắc C ++ kỳ lạ mà tôi không tuân theo

Câu trả lời:


301

Một hàm thành viên không tĩnh phải được gọi với một đối tượng. Đó là, nó luôn ngầm chuyển con trỏ "này" làm đối số của nó.

std::functionchữ ký của bạn chỉ định rằng hàm của bạn không nhận bất kỳ đối số ( <void(void)>) nào, nên bạn phải liên kết đối số đầu tiên (và duy nhất).

std::function<void(void)> f = std::bind(&Foo::doSomething, this);

Nếu bạn muốn liên kết một chức năng với các tham số, bạn cần chỉ định giữ chỗ:

using namespace std::placeholders;
std::function<void(int,int)> f = std::bind(&Foo::doSomethingArgs, this, std::placeholders::_1, std::placeholders::_2);

Hoặc, nếu trình biên dịch của bạn hỗ trợ C ++ 11 lambdas:

std::function<void(int,int)> f = [=](int a, int b) {
    this->doSomethingArgs(a, b);
}

(Tôi không có một C ++ 11 trình biên dịch có khả năng trong tầm tay ngay bây giờ , vì vậy tôi không thể kiểm tra thế này.)


1
Vì tôi không phụ thuộc vào boost nên tôi sẽ sử dụng biểu thức lambda;) Tuy nhiên cảm ơn!
Christian Ivicevic

3
@AlexB: Boost.Bind không sử dụng ADL cho trình giữ chỗ, nó đặt chúng trong một không gian tên ẩn danh.
ildjarn

46
Tôi khuyên bạn nên tránh chụp toàn cầu [=] và sử dụng [này] để làm rõ hơn những gì đã chụp (Scott Meyers - Modern C ++ Chương 6. mục 31 - Tránh các chế độ chụp mặc định)
Max Raskin

5
Chỉ cần thêm một mẹo nhỏ: con trỏ hàm thành viên có thể được sử dụng hoàn toàn std::function, với thêm thislà tham số đầu tiên, nhưstd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
Landantlyoung

@landerlyoung: Thêm tên của hàm nói là "f" ở trên để sửa cú pháp mẫu. Nếu bạn không cần tên, bạn có thể sử dụng mem_fn (& Foo :: doS SomethingArss).
Val

80

Hoặc bạn cần

std::function<void(Foo*)> f = &Foo::doSomething;

để bạn có thể gọi nó trong bất kỳ trường hợp nào, hoặc bạn cần liên kết một trường hợp cụ thể, ví dụ this

std::function<void(void)> f = std::bind(&Foo::doSomething, this);

Cảm ơn bạn vì câu trả lời tuyệt vời này: D Chính xác những gì tôi cần, tôi không thể tìm cách chuyên môn hóa một hàm std :: để gọi một hàm thành viên trên bất kỳ thể hiện lớp nào.
Penelope

Điều này biên dịch, nhưng nó là tiêu chuẩn? Bạn có đảm bảo rằng đối số đầu tiên là this?
sudo rm -rf chém

@ sudorm-rfslash vâng, bạn là
Armen Tsirunyan

Cảm ơn bạn đã trả lời @ArmenTsirunyan ... tôi có thể tìm thông tin này ở đâu trong tiêu chuẩn?
sudo rm -rf chém

13

Nếu bạn cần lưu trữ một hàm thành viên mà không có thể hiện của lớp, bạn có thể làm một cái gì đó như thế này:

class MyClass
{
public:
    void MemberFunc(int value)
    {
      //do something
    }
};

// Store member function binding
auto callable = std::mem_fn(&MyClass::MemberFunc);

// Call with late supplied 'this'
MyClass myInst;
callable(&myInst, 123);

Kiểu lưu trữ sẽ trông như thế nào nếu không có tự động ? Một cái gì đó như thế này:

std::_Mem_fn_wrap<void,void (__cdecl TestA::*)(int),TestA,int> callable

Bạn cũng có thể truyền lưu trữ chức năng này cho một ràng buộc chức năng tiêu chuẩn

std::function<void(int)> binding = std::bind(callable, &testA, std::placeholders::_1);
binding(123); // Call

Ghi chú trong quá khứ và tương lai: Một giao diện cũ hơn std :: mem_func đã tồn tại, nhưng đã bị từ chối . Một đề xuất tồn tại, đăng C ++ 17, để tạo con trỏ tới các hàm thành viên có thể gọi được . Điều này sẽ được chào đón nhất.


@Danh std::mem_fnđã không loại bỏ; một loạt các quá tải không cần thiết là. Mặt khác, std::mem_funkhông được chấp nhận với C ++ 11 và sẽ bị xóa bằng C ++ 17.
Max Truxa

@Danh Đó chính xác là những gì tôi đang nói về;) Quá tải "cơ bản" đầu tiên vẫn còn đó: template<class R, class T> unspecified mem_fn(R T::*);và nó sẽ không biến mất.
Max Truxa

@Danh Đọc DR cẩn thận. 12 trong số 13 quá tải đã được DR loại bỏ. Cái cuối cùng không phải (và sẽ không có trong C ++ 11 hoặc C ++ 14).
Max Truxa

1
Tại sao bỏ phiếu xuống? Mọi phản hồi khác đều nói rằng bạn phải ràng buộc thể hiện của lớp. Nếu bạn đang tạo một hệ thống ràng buộc để phản ánh hoặc viết kịch bản, bạn sẽ không muốn làm điều đó. Phương pháp thay thế này là hợp lệ và phù hợp với một số người.
Greg

Cảm ơn Danh, tôi đã chỉnh sửa một số nhận xét về các giao diện trong quá khứ và tương lai.
Greg

3

Thật không may, C ++ không cho phép bạn trực tiếp lấy một đối tượng có thể gọi được tham chiếu đến một đối tượng và một trong các hàm thành viên của nó. &Foo::doSomethingcung cấp cho bạn "con trỏ tới hàm thành viên" dùng để chỉ hàm thành viên chứ không phải đối tượng liên quan.

Có hai cách xung quanh vấn đề này, một là sử dụng std::bindđể liên kết "con trỏ tới hàm thành viên" với thiscon trỏ. Cách khác là sử dụng lambda để bắt thiscon trỏ và gọi hàm thành viên.

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
std::function<void(void)> g = [this](){doSomething();};

Tôi thích cái sau

Với g ++ ít nhất ràng buộc một hàm thành viên với điều này sẽ dẫn đến một đối tượng có ba con trỏ có kích thước, việc gán nó cho một std::functionkết quả sẽ dẫn đến việc cấp phát bộ nhớ động.

Mặt khác, một lambda thu thisđược chỉ có một con trỏ, gán nó cho một std::functionsẽ không dẫn đến việc cấp phát bộ nhớ động với g ++.

Mặc dù tôi chưa xác minh điều này với các trình biên dịch khác, tôi nghi ngờ kết quả tương tự sẽ được tìm thấy ở đó.


1

Bạn có thể sử dụng functor nếu bạn muốn một điều khiển ít chung chung và chính xác hơn dưới mui xe. Ví dụ với api win32 của tôi để chuyển tiếp tin nhắn api từ một lớp sang một lớp khác.

IListener.h

#include <windows.h>
class IListener { 
    public:
    virtual ~IListener() {}
    virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
};

Người nghe.h

#include "IListener.h"
template <typename D> class Listener : public IListener {
    public:
    typedef LRESULT (D::*WMFuncPtr)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 

    private:
    D* _instance;
    WMFuncPtr _wmFuncPtr; 

    public:
    virtual ~Listener() {}
    virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) override {
        return (_instance->*_wmFuncPtr)(hWnd, uMsg, wParam, lParam);
    }

    Listener(D* instance, WMFuncPtr wmFuncPtr) {
        _instance = instance;
        _wmFuncPtr = wmFuncPtr;
    }
};

Người điều phối

#include <map>
#include "Listener.h"

class Dispatcher {
    private:
        //Storage map for message/pointers
        std::map<UINT /*WM_MESSAGE*/, IListener*> _listeners; 

    public:
        virtual ~Dispatcher() { //clear the map }

        //Return a previously registered callable funtion pointer for uMsg.
        IListener* get(UINT uMsg) {
            typename std::map<UINT, IListener*>::iterator itEvt;
            if((itEvt = _listeners.find(uMsg)) == _listeners.end()) {
                return NULL;
            }
            return itEvt->second;
        }

        //Set a member function to receive message. 
        //Example Button->add<MyClass>(WM_COMMAND, this, &MyClass::myfunc);
        template <typename D> void add(UINT uMsg, D* instance, typename Listener<D>::WMFuncPtr wmFuncPtr) {
            _listeners[uMsg] = new Listener<D>(instance, wmFuncPtr);
        }

};

Nguyên tắc sử dụng

class Button {
    public:
    Dispatcher _dispatcher;
    //button window forward all received message to a listener
    LRESULT onMessage(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
        //to return a precise message like WM_CREATE, you have just
        //search it in the map.
        return _dispatcher[uMsg](hWnd, uMsg, w, l);
    }
};

class Myclass {
    Button _button;
    //the listener for Button messages
    LRESULT button_listener(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
        return 0;
    }

    //Register the listener for Button messages
    void initialize() {
        //now all message received from button are forwarded to button_listener function 
       _button._dispatcher.add(WM_CREATE, this, &Myclass::button_listener);
    }
};

Chúc may mắn và cảm ơn tất cả đã chia sẻ kiến ​​thức.


0

Bạn có thể tránh std::bindlàm điều này:

std::function<void(void)> f = [this]-> {Foo::doSomething();}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.