Con trỏ hàm đến hàm thành viên


89

Tôi muốn thiết lập một con trỏ hàm làm thành viên của một lớp là một con trỏ tới một hàm khác trong cùng một lớp. Lý do tại sao tôi làm điều này rất phức tạp.

Trong ví dụ này, tôi muốn đầu ra là "1"

class A {
public:
 int f();
 int (*x)();
}

int A::f() {
 return 1;
}


int main() {
 A a;
 a.x = a.f;
 printf("%d\n",a.x())
}

Nhưng điều này không thành công khi biên dịch. Tại sao?



@jww và kiểm tra câu trả lời của CiroSantilli trong câu hỏi đó, các câu trả lời khác ít nhiều lạc đề. Về cơ bản, chỉ cần int (C :: * function_pointer_var) (int) = & C :: method; thì C c; và (c. * function_pointer_var) (2).
jw_

Câu trả lời:


157

Cú pháp sai. Con trỏ thành viên là một loại kiểu khác với một con trỏ thông thường. Con trỏ thành viên sẽ phải được sử dụng cùng với một đối tượng của lớp nó:

class A {
public:
 int f();
 int (A::*x)(); // <- declare by saying what class it is a pointer to
};

int A::f() {
 return 1;
}


int main() {
 A a;
 a.x = &A::f; // use the :: syntax
 printf("%d\n",(a.*(a.x))()); // use together with an object of its class
}

a.xchưa nói hàm được gọi trên đối tượng nào. Nó chỉ nói rằng bạn muốn sử dụng con trỏ được lưu trữ trong đối tượng a. Đặt trước amột thời gian khác khi toán hạng bên trái cho toán .*tử sẽ thông báo cho trình biên dịch biết đối tượng nào sẽ gọi hàm.


Tôi biết điều này là cũ, nhưng tôi không hiểu cách sử dụng (a.*a.x)(). Tại sao (a.*x)()không hoạt động?
Gaurav Sehgal

3
@gau vì x không nằm trong phạm vi
Johannes Schaub - litb

13
Tôi cũng phải tra cứu cái này mỗi khi tôi sử dụng nó. Cú pháp khó hiểu, nhưng nó sẽ có ý nghĩa nếu bạn chia nhỏ nó. a.xlà một con trỏ tới một hàm thành viên của lớp A. bỏ *a.xtham chiếu đến con trỏ nên bây giờ nó là một tham chiếu hàm. a.(*a.x)"liên kết" hàm với một thể hiện (giống như a.f). (a.(*a.x))là cần thiết để nhóm cú pháp phức tạp này và (a.(*a.x))()thực sự gọi phương thức trên amà không có đối số.
jwm

23

int (*x)()không phải là một con trỏ đến chức năng thành viên. Một con trỏ tới hàm thành viên được viết như thế này: int (A::*x)(void) = &A::f;.


17

Gọi hàm thành viên trên lệnh chuỗi

#include <iostream>
#include <string>


class A 
{
public: 
    void call();
private:
    void printH();
    void command(std::string a, std::string b, void (A::*func)());
};

void A::printH()
{
    std::cout<< "H\n";
}

void A::call()
{
    command("a","a", &A::printH);
}

void A::command(std::string a, std::string b, void (A::*func)())
{
    if(a == b)
    {
        (this->*func)();
    }
}

int main()
{
    A a;
    a.call();
    return 0;
}

Chú ý đến (this->*func)();và cách khai báo con trỏ hàm với tên lớpvoid (A::*func)()


11

Bạn cần sử dụng một con trỏ đến một hàm thành viên, không chỉ một con trỏ đến một hàm.

class A { 
    int f() { return 1; }
public:
    int (A::*x)();

    A() : x(&A::f) {}
};

int main() { 
   A a;
   std::cout << (a.*a.x)();
   return 0;
}

3

Mặc dù điều này dựa trên các câu trả lời sterling ở những nơi khác trên trang này, nhưng tôi có một trường hợp sử dụng không được chúng giải quyết hoàn toàn; đối với một vectơ của con trỏ đến các hàm, hãy làm như sau:

#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdlib.h>

class A{
public:
  typedef vector<int> (A::*AFunc)(int I1,int I2);
  vector<AFunc> FuncList;
  inline int Subtract(int I1,int I2){return I1-I2;};
  inline int Add(int I1,int I2){return I1+I2;};
  ...
  void Populate();
  void ExecuteAll();
};

void A::Populate(){
    FuncList.push_back(&A::Subtract);
    FuncList.push_back(&A::Add);
    ...
}

void A::ExecuteAll(){
  int In1=1,In2=2,Out=0;
  for(size_t FuncId=0;FuncId<FuncList.size();FuncId++){
    Out=(this->*FuncList[FuncId])(In1,In2);
    printf("Function %ld output %d\n",FuncId,Out);
  }
}

int main(){
  A Demo;
  Demo.Populate();
  Demo.ExecuteAll();
  return 0;
}

Một cái gì đó như thế này rất hữu ích nếu bạn đang viết một trình thông dịch lệnh với các hàm được lập chỉ mục cần kết hợp với cú pháp tham số và các mẹo trợ giúp, v.v. Có thể cũng hữu ích trong các menu.


1
Như đã định nghĩa, AFunc là một con trỏ tới hàm thành viên lấy hai số nguyên và trả về một vectơ gồm các số nguyên. Nhưng các thành viên chỉ trả về int, phải không? Tôi nghĩ câu lệnh typedef nên là typedef int (A::*AFunc)(int I1,int I2);
riderBill

2

Mặc dù không may là bạn không thể chuyển đổi một con trỏ hàm thành viên hiện có thành một con trỏ hàm thuần túy, nhưng bạn có thể tạo một mẫu hàm bộ điều hợp theo cách khá đơn giản bao bọc một con trỏ hàm thành viên đã biết tại thời điểm biên dịch trong một hàm bình thường như sau:

template <class Type>
struct member_function;

template <class Type, class Ret, class... Args>
struct member_function<Ret(Type::*)(Args...)>
{
    template <Ret(Type::*Func)(Args...)>
    static Ret adapter(Type &obj, Args&&... args)
    {
        return (obj.*Func)(std::forward<Args>(args)...);
    }
};

template <class Type, class Ret, class... Args>
struct member_function<Ret(Type::*)(Args...) const>
{
    template <Ret(Type::*Func)(Args...) const>
    static Ret adapter(const Type &obj, Args&&... args)
    {
        return (obj.*Func)(std::forward<Args>(args)...);
    }
};

 

int (*func)(A&) = &member_function<decltype(&A::f)>::adapter<&A::f>;

Lưu ý rằng để gọi hàm thành viên, một thể hiện của Aphải được cung cấp.


Bạn đã truyền cảm hứng cho tôi, @ IllidanS4. Hãy xem câu trả lời của tôi. +1
memtha

1

Dựa trên câu trả lời của @ IllidanS4, tôi đã tạo một lớp mẫu cho phép hầu như bất kỳ hàm thành viên nào với các đối số được xác định trước và cá thể lớp được chuyển qua tham chiếu để gọi sau.



template<class RET, class... RArgs> class Callback_t {
public:
    virtual RET call(RArgs&&... rargs) = 0;
    //virtual RET call() = 0;
};

template<class T, class RET, class... RArgs> class CallbackCalltimeArgs : public Callback_t<RET, RArgs...> {
public:
    T * owner;
    RET(T::*x)(RArgs...);
    RET call(RArgs&&... rargs) {
        return (*owner.*(x))(std::forward<RArgs>(rargs)...);
    };
    CallbackCalltimeArgs(T* t, RET(T::*x)(RArgs...)) : owner(t), x(x) {}
};

template<class T, class RET, class... Args> class CallbackCreattimeArgs : public Callback_t<RET> {
public:
    T* owner;
    RET(T::*x)(Args...);
    RET call() {
        return (*owner.*(x))(std::get<Args&&>(args)...);
    };
    std::tuple<Args&&...> args;
    CallbackCreattimeArgs(T* t, RET(T::*x)(Args...), Args&&... args) : owner(t), x(x),
        args(std::tuple<Args&&...>(std::forward<Args>(args)...)) {}
};

Kiểm tra / ví dụ:

class container {
public:
    static void printFrom(container* c) { c->print(); };
    container(int data) : data(data) {};
    ~container() {};
    void print() { printf("%d\n", data); };
    void printTo(FILE* f) { fprintf(f, "%d\n", data); };
    void printWith(int arg) { printf("%d:%d\n", data, arg); };
private:
    int data;
};

int main() {
    container c1(1), c2(20);
    CallbackCreattimeArgs<container, void> f1(&c1, &container::print);
    Callback_t<void>* fp1 = &f1;
    fp1->call();//1
    CallbackCreattimeArgs<container, void, FILE*> f2(&c2, &container::printTo, stdout);
    Callback_t<void>* fp2 = &f2;
    fp2->call();//20
    CallbackCalltimeArgs<container, void, int> f3(&c2, &container::printWith);
    Callback_t<void, int>* fp3 = &f3;
    fp3->call(15);//20:15
}

Rõ ràng, điều này sẽ chỉ hoạt động nếu các đối số đã cho và lớp chủ sở hữu vẫn hợp lệ. Theo như khả năng đọc được ... xin hãy thứ lỗi cho tôi.

Chỉnh sửa: loại bỏ malloc không cần thiết bằng cách làm cho bộ lưu trữ bình thường. Đã thêm loại kế thừa cho tham chiếu. Đã thêm tùy chọn để cung cấp tất cả các đối số vào thời gian gọi thay thế. Bây giờ đang làm việc để có cả hai ....

Chỉnh sửa 2: Như đã hứa, cả hai. Chỉ có một hạn chế (mà tôi thấy) là các đối số được xác định trước phải đến trước các đối số được cung cấp thời gian chạy trong hàm gọi lại. Cảm ơn @Chipster về một số trợ giúp trong việc tuân thủ gcc. Điều này hoạt động trên gcc trên ubuntu và studio trực quan trên windows.

#ifdef _WIN32
#define wintypename typename
#else
#define wintypename
#endif

template<class RET, class... RArgs> class Callback_t {
public:
    virtual RET call(RArgs... rargs) = 0;
    virtual ~Callback_t() = default;
};

template<class RET, class... RArgs> class CallbackFactory {
private:
    template<class T, class... CArgs> class Callback : public Callback_t<RET, RArgs...> {
    private:
        T * owner;
        RET(T::*x)(CArgs..., RArgs...);
        std::tuple<CArgs...> cargs;
        RET call(RArgs... rargs) {
            return (*owner.*(x))(std::get<CArgs>(cargs)..., rargs...);
        };
    public:
        Callback(T* t, RET(T::*x)(CArgs..., RArgs...), CArgs... pda);
        ~Callback() {};
    };
public:
    template<class U, class... CArgs> static Callback_t<RET, RArgs...>* make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...));
};
template<class RET2, class... RArgs2> template<class T2, class... CArgs2> CallbackFactory<RET2, RArgs2...>::Callback<T2, CArgs2...>::Callback(T2* t, RET2(T2::*x)(CArgs2..., RArgs2...), CArgs2... pda) : x(x), owner(t), cargs(std::forward<CArgs2>(pda)...) {}
template<class RET, class... RArgs> template<class U, class... CArgs> Callback_t<RET, RArgs...>* CallbackFactory<RET, RArgs...>::make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...)) {
    return new wintypename CallbackFactory<RET, RArgs...>::Callback<U, CArgs...>(owner, func, std::forward<CArgs>(cargs)...);
}
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.