Chức năng của C ++ là gì và công dụng của chúng là gì?


876

Tôi tiếp tục nghe rất nhiều về functor trong C ++. Ai đó có thể cho tôi một cái nhìn tổng quan về những gì họ đang có và trong những trường hợp họ sẽ hữu ích?


4
Chủ đề này đã được đề cập để trả lời cho câu hỏi này: stackoverflow.com/questions/317450/why-override-operator#317528
Luc Touraille

2
Nó được sử dụng để tạo một bao đóng trong C ++.
Ngày

Nhìn vào các câu trả lời dưới đây, nếu ai đó đang tự hỏi điều đó operator()(...)có nghĩa là gì : nó đang làm quá tải toán tử "gọi hàm" . Nó chỉ đơn giản là quá tải toán tử cho ()toán tử. Đừng nhầm operator()với cách gọi một hàm được gọi operator, nhưng hãy xem nó như cú pháp nạp chồng toán tử thông thường.
zardosht

Câu trả lời:


1041

Một functor gần như chỉ là một lớp định nghĩa toán tử (). Điều đó cho phép bạn tạo các đối tượng "trông giống như" một hàm:

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

Có một vài điều tốt đẹp về functor. Một là không giống như các chức năng thông thường, chúng có thể chứa trạng thái. Ví dụ trên tạo ra một hàm bổ sung 42 vào bất cứ thứ gì bạn cung cấp cho nó. Nhưng giá trị 42 đó không được mã hóa cứng, nó được chỉ định làm đối số hàm tạo khi chúng ta tạo đối tượng functor. Tôi có thể tạo một bộ cộng khác, bổ sung 27, chỉ bằng cách gọi hàm tạo với giá trị khác. Điều này làm cho chúng tùy biến độc đáo.

Như các dòng cuối cùng hiển thị, bạn thường truyền functor làm đối số cho các hàm khác như std :: Transform hoặc các thuật toán thư viện chuẩn khác. Bạn có thể làm tương tự với một con trỏ hàm thông thường ngoại trừ, như tôi đã nói ở trên, functor có thể được "tùy chỉnh" vì chúng chứa trạng thái, làm cho chúng linh hoạt hơn (Nếu tôi muốn sử dụng một con trỏ hàm, tôi phải viết một hàm mà thêm chính xác 1 vào đối số của nó. Functor là chung và thêm bất cứ điều gì bạn khởi tạo nó với), và chúng cũng có khả năng hiệu quả hơn. Trong ví dụ trên, trình biên dịch biết chính xác hàm nào std::transformsẽ gọi. Nó nên gọi add_x::operator(). Điều đó có nghĩa là nó có thể nội tuyến gọi chức năng đó. Và điều đó làm cho nó hiệu quả như thể tôi đã gọi hàm theo cách thủ công trên mỗi giá trị của vectơ.

Nếu tôi đã chuyển một con trỏ hàm thay vào đó, trình biên dịch không thể thấy ngay hàm nào nó trỏ đến, vì vậy trừ khi nó thực hiện một số tối ưu hóa toàn cầu khá phức tạp, nó sẽ phải hủy bỏ con trỏ trong thời gian chạy, sau đó thực hiện cuộc gọi.


32
Bạn có thể giải thích dòng này không, vui lòng std :: Transform (in.begin (), in.end (), out.begin (), add_x (1)); Tại sao bạn viết ở đó add_x chứ không phải add42?
Alecs

102
@Alecs Cả hai sẽ hoạt động (nhưng hiệu quả sẽ khác nhau). Nếu tôi đã sử dụng add42, tôi đã sử dụng functor mà tôi đã tạo trước đó và thêm 42 vào mỗi giá trị. Với add_x(1)tôi tạo một phiên bản mới của functor, một phiên bản chỉ thêm 1 cho mỗi giá trị. Nó chỉ đơn giản là để cho thấy rằng thường xuyên, bạn khởi tạo functor "một cách nhanh chóng", khi bạn cần nó, thay vì tạo nó trước, và giữ nó xung quanh trước khi bạn thực sự sử dụng nó cho bất cứ điều gì.
jalf

8
@zadane dĩ nhiên rồi. Họ chỉ cần có operator(), bởi vì đó là những gì người gọi sử dụng để gọi nó. Những gì khác functor có các hàm thành viên, hàm tạo, toán tử và biến thành viên hoàn toàn phụ thuộc vào bạn.
jalf

4
@ rikimaru2013 Theo cách nói của lập trình hàm, bạn đã đúng, một hàm cũng là hàm functor, nhưng theo cách nói của C ++, functor đặc biệt là một lớp được sử dụng như một hàm. Thuật ngữ này đã bị lạm dụng một chút từ sớm, nhưng sự phân chia này là sự phân biệt hữu ích và vì vậy vẫn tồn tại đến ngày nay. Nếu bạn bắt đầu đề cập đến các chức năng là "functor" trong ngữ cảnh C ++ thì bạn sẽ chỉ nhầm lẫn cuộc trò chuyện.
srm

6
Nó là một lớp hoặc một thể hiện của lớp? Trong hầu hết các nguồn, add42sẽ được gọi là functor, không phải add_x(đó là lớp của functor hoặc chỉ là lớp functor). Tôi thấy rằng thuật ngữ này phù hợp bởi vì functor cũng được gọi là các đối tượng hàm , không phải các lớp chức năng. Bạn có thể làm rõ điểm này?
Sergei Tachenov

121

Ít bổ sung. Bạn có thể sử dụng boost::function, để tạo functor từ các hàm và phương thức, như thế này:

class Foo
{
public:
    void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"

và bạn có thể sử dụng boost :: bind để thêm trạng thái cho functor này

boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"

và hữu ích nhất, với hàm boost :: bind và boost :: bạn có thể tạo functor từ phương thức lớp, đây thực sự là một đại biểu:

class SomeClass
{
    std::string state_;
public:
    SomeClass(const char* s) : state_(s) {}

    void method( std::string param )
    {
        std::cout << state_ << param << std::endl;
    }
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"

Bạn có thể tạo danh sách hoặc vector của functor

std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
        events.begin(), events.end(), 
        boost::bind( boost::apply<void>(), _1, e));

Có một vấn đề với tất cả những thứ này, thông báo lỗi trình biên dịch không thể đọc được :)


4
Không nên operator ()công khai trong ví dụ đầu tiên của bạn vì các lớp mặc định là riêng tư?
NathanOliver

4
có thể tại một thời điểm nào đó câu trả lời này xứng đáng được cập nhật, vì bây giờ lambdas là cách dễ nhất để có được một functor từ bất cứ điều gì
idclev 463035818

102

Một Functor là một đối tượng hoạt động như một hàm. Về cơ bản, một lớp định nghĩa operator().

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

Lợi thế thực sự là một functor có thể giữ trạng thái.

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}

11
Chỉ cần thêm rằng chúng có thể được sử dụng giống như một con trỏ hàm.
Martin York

7
@LokiAstari - Đối với những người chưa quen với khái niệm này, điều đó có thể hơi sai lệch. Functor có thể được "sử dụng như", nhưng không phải luôn luôn "thay thế" các con trỏ hàm. Ví dụ, một hàm lấy một con trỏ hàm không thể lấy functor ở vị trí của nó ngay cả khi functor có cùng các đối số và trả về giá trị như con trỏ hàm. Nhưng nhìn chung khi thiết kế, functor là cách tốt nhất và hiện đại hơn về mặt lý thuyết.
MasonWinsauer

Tại sao cái thứ hai trở lại intkhi nó nên trở lại bool? Đây là C ++, không phải C. Khi câu trả lời này được viết, boolkhông tồn tại?
Vụ kiện của Quỹ Monica

@QPaysTaxes Tôi đoán là một lỗi đánh máy. Tôi có thể đã sao chép mã từ ví dụ đầu tiên và quên thay đổi nó. Tôi đã sửa nó rồi.
James Curran

1
@Riasat Nếu Matcher nằm trong thư viện, việc xác định Is5 () khá đơn giản. ANd bạn có thể tạo Is7 (), Is32 (), v.v. Hơn nữa, đó chỉ là một ví dụ. Functor có thể phức tạp hơn nhiều.
James Curran

51

Tên "functor" đã được sử dụng trong lý thuyết thể loại từ lâu trước khi C ++ xuất hiện trên hiện trường. Điều này không liên quan gì đến khái niệm C ++ của functor. Tốt hơn là sử dụng đối tượng hàm tên thay vì cái mà chúng ta gọi là "functor" trong C ++. Đây là cách các ngôn ngữ lập trình khác gọi các cấu trúc tương tự.

Được sử dụng thay cho chức năng đơn giản:

Đặc trưng:

  • Đối tượng chức năng có thể có trạng thái
  • Đối tượng chức năng phù hợp với OOP (nó hoạt động như mọi đối tượng khác).

Nhược điểm:

  • Mang lại sự phức tạp hơn cho chương trình.

Được sử dụng thay cho con trỏ hàm:

Đặc trưng:

  • Đối tượng chức năng thường có thể được nội tuyến

Nhược điểm:

  • Không thể hoán đổi đối tượng hàm với loại đối tượng hàm khác trong thời gian chạy (ít nhất là trừ khi nó mở rộng một số lớp cơ sở, do đó cung cấp một số chi phí)

Được sử dụng thay cho chức năng ảo:

Đặc trưng:

  • Đối tượng chức năng (không ảo) không yêu cầu gửi vtable và thời gian chạy, do đó, nó hiệu quả hơn trong hầu hết các trường hợp

Nhược điểm:

  • Không thể hoán đổi đối tượng hàm với loại đối tượng hàm khác trong thời gian chạy (ít nhất là trừ khi nó mở rộng một số lớp cơ sở, do đó cung cấp một số chi phí)

1
Bạn có thể giải thích những trường hợp sử dụng trong ví dụ thực tế? Làm thế nào chúng ta có thể sử dụng functor làm con trỏ đa hình adn funtion?
Milad Khajavi

1
Điều gì thực sự có nghĩa là một functor giữ trạng thái?
erogol

cảm ơn vì đã chỉ ra rằng người ta cần một lớp cơ sở để có một số loại đa hình. Tôi chỉ gặp vấn đề là tôi phải sử dụng một functor ở cùng một nơi với một con trỏ hàm đơn giản và cách duy nhất tôi tìm thấy là viết một lớp cơ sở functor (vì tôi không thể sử dụng công cụ C ++ 11). Không chắc chắn nếu chi phí này có ý nghĩa cho đến khi tôi đọc câu trả lời của bạn.
idclev 463035818

1
@Erogol Một functor là một đối tượng xảy ra để hỗ trợ cú pháp foo(arguments). Do đó, nó có thể chứa các biến; ví dụ, nếu bạn có một update_password(string)chức năng, bạn có thể muốn theo dõi tần suất xảy ra; với một functor, đó có thể là dấu hiệu private long timeđại diện cho dấu thời gian nó xảy ra lần trước. Với một con trỏ hàm hoặc chức năng đơn giản, bạn sẽ cần phải sử dụng một bên ngoài biến của không gian tên của nó, mà chỉ liên quan trực tiếp bởi các tài liệu và sử dụng, chứ không phải bởi definition.l
Vụ kiện Quỹ Monica

4
Để đề cập rằng tên đã được tạo ra không có lý do. Tôi vừa tìm kiếm mối quan hệ giữa functor toán học (hoặc chức năng nếu bạn muốn) và functor từ C ++.
Hi-Angel

41

Giống như những người khác đã đề cập, functor là một đối tượng hoạt động như một hàm, tức là nó làm quá tải toán tử gọi hàm.

Functor thường được sử dụng trong các thuật toán STL. Chúng rất hữu ích vì chúng có thể giữ trạng thái trước và giữa các lệnh gọi hàm, giống như đóng trong các ngôn ngữ chức năng. Ví dụ: bạn có thể định nghĩa một MultiplyByfunctor nhân đối số của nó với một lượng được chỉ định:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

Sau đó, bạn có thể chuyển một MultiplyByđối tượng cho một thuật toán như std :: Transform:

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

Một ưu điểm khác của functor so với con trỏ tới hàm là cuộc gọi có thể được nội tuyến trong nhiều trường hợp hơn. Nếu bạn đã chuyển một con trỏ hàm đến transform, trừ khi cuộc gọi đó được nội tuyến và trình biên dịch biết rằng bạn luôn truyền cùng chức năng cho nó, nó không thể thực hiện cuộc gọi thông qua con trỏ.


37

Đối với những người mới như tôi trong số chúng tôi: sau một nghiên cứu nhỏ, tôi đã tìm ra những gì mã jalf đã đăng.

Một functor là một đối tượng lớp hoặc struct có thể được "gọi" giống như một hàm. Điều này được thực hiện bằng cách quá tải () operator. Các () operator(không chắc chắn những gì gọi của nó) có thể mất bất kỳ số lượng các đối số. Các toán tử khác chỉ lấy hai tức là + operatorchỉ có thể lấy hai giá trị (một giá trị ở mỗi bên của toán tử) và trả về bất kỳ giá trị nào bạn đã nạp cho nó. Bạn có thể phù hợp với bất kỳ số lượng đối số bên trong () operatorđó là những gì mang lại cho nó tính linh hoạt.

Để tạo một functor trước tiên bạn tạo lớp của bạn. Sau đó, bạn tạo một hàm tạo cho lớp với tham số bạn chọn loại và tên. Điều này được theo sau trong cùng một câu lệnh bởi một danh sách khởi tạo (sử dụng một toán tử dấu hai chấm, một cái gì đó tôi cũng mới) để xây dựng các đối tượng thành viên lớp với tham số được khai báo trước đó cho hàm tạo. Sau đó () operatorlà quá tải. Cuối cùng, bạn khai báo các đối tượng riêng của lớp hoặc struct bạn đã tạo.

Mã của tôi (tôi thấy tên biến của jalf khó hiểu)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

Nếu bất kỳ điều này là không chính xác hoặc chỉ đơn giản là sai, hãy sửa tôi!


1
Toán tử () được gọi là toán tử gọi hàm. Tôi đoán bạn cũng có thể gọi nó là toán tử dấu ngoặc đơn.
Gautam

4
"Tham số này thực sự là đối số" tham sốVar "được truyền bởi hàm tạo mà chúng ta vừa viết" Huh?
Các cuộc đua nhẹ nhàng trong quỹ đạo

22

Một functor là một hàm bậc cao hơn , áp dụng một hàm cho các kiểu tham số (tức là templated). Đây là một khái quát của chức năng bậc cao hơn của bản đồ . Ví dụ: chúng ta có thể định nghĩa một functor std::vectornhư thế này:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

Hàm này nhận a std::vector<T>và trả về std::vector<U>khi được cung cấp một hàm Flấy a Tvà trả về a U. Một functor không cần phải được xác định trên các loại container, nó cũng có thể được xác định cho bất kỳ loại templated nào, bao gồm std::shared_ptr:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

Đây là một ví dụ đơn giản chuyển đổi loại thành double:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

Có hai luật mà functor nên tuân theo. Đầu tiên là luật định danh, quy định rằng nếu functor được cung cấp một chức năng nhận dạng, thì nó sẽ giống như áp dụng chức năng nhận dạng cho loại, điều đó fmap(identity, x)sẽ giống như identity(x):

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

Luật tiếp theo là luật thành phần, quy định rằng nếu functor được cung cấp một thành phần gồm hai hàm, thì nó sẽ giống như áp dụng hàm functor cho hàm thứ nhất và sau đó lại cho hàm thứ hai. Vì vậy, fmap(std::bind(f, std::bind(g, _1)), x)nên giống như fmap(f, fmap(g, x)):

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

2
Bài viết lập luận rằng functor nên được sử dụng chính xác cho ý nghĩa này (xem thêm en.wikipedia.org/wiki/Functor ) và việc sử dụng nó cho các đối tượng chức năng chỉ là cẩu thả: jackieokay.com/2017/01/26/functor.html Nó có thể là quá muộn cho điều đó, mặc dù số lượng câu trả lời ở đây chỉ xem xét ý nghĩa của đối tượng hàm.
armb

2
Câu trả lời này phải là câu trả lời có> 700 Upvotes. Là một người biết Haskell tốt hơn C ++, lingua C ++ làm tôi bối rối mọi lúc.
mschmidt

Thể loại lý thuyết và C ++? Đây có phải là tài khoản SO bí mật của Bartosz Milewski không?
Mateen Ulhaq

1
Có thể hữu ích để tóm tắt các luật functor trong ký hiệu chuẩn: fmap(id, x) = id(x)fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq

@mschmidt trong khi functor cũng có nghĩa như vậy, C ++ làm quá tải tên có nghĩa giống như "đối tượng chức năng"
Caleth

9

Đây là một tình huống thực tế khi tôi buộc phải sử dụng Functor để giải quyết vấn đề của mình:

Tôi có một tập hợp các hàm (giả sử, 20 trong số chúng) và chúng đều giống hệt nhau, ngoại trừ mỗi lệnh gọi một hàm cụ thể khác nhau trong 3 điểm cụ thể.

Đây là sự lãng phí đáng kinh ngạc, và sao chép mã. Thông thường tôi sẽ chỉ truyền vào một con trỏ hàm và chỉ gọi nó trong 3 điểm. (Vì vậy, mã chỉ cần xuất hiện một lần, thay vì hai mươi lần.)

Nhưng sau đó tôi nhận ra, trong mỗi trường hợp, hàm cụ thể yêu cầu một cấu hình tham số hoàn toàn khác nhau! Đôi khi 2 tham số, đôi khi 5 tham số, v.v.

Một giải pháp khác là có một lớp cơ sở, trong đó hàm cụ thể là một phương thức được ghi đè trong lớp dẫn xuất. Nhưng tôi có thực sự muốn xây dựng tất cả INHERITANCE này không, để tôi có thể vượt qua một con trỏ hàm ????

GIẢI PHÁP: Vì vậy, những gì tôi đã làm là, tôi đã tạo một lớp bao bọc ("Functor") có thể gọi bất kỳ hàm nào tôi cần gọi. Tôi thiết lập nó trước (với các tham số của nó, v.v.) và sau đó tôi chuyển nó vào thay vì một con trỏ hàm. Bây giờ mã được gọi có thể kích hoạt Functor, mà không biết những gì đang xảy ra ở bên trong. Nó thậm chí có thể gọi nó nhiều lần (tôi cần nó để gọi 3 lần.)


Đó là nó - một ví dụ thực tế trong đó Functor hóa ra là giải pháp rõ ràng và dễ dàng, cho phép tôi giảm sao chép mã từ 20 hàm xuống 1.


3
Nếu functor của bạn gọi các hàm cụ thể khác nhau và các hàm khác này khác nhau về số lượng tham số mà chúng chấp nhận, điều này có nghĩa là functor của bạn chấp nhận một số lượng đối số khác nhau để gửi đến các hàm khác này?
johnbakers

4
bạn có thể giải thích kịch bản trên bằng cách trích dẫn một phần của mã không, tôi mới biết về c ++ muốn hiểu khái niệm này ..
sanjeev

3

Ngoại trừ được sử dụng trong gọi lại, functor C ++ cũng có thể giúp cung cấp kiểu truy cập Matlab thích một lớp ma trận . Có một ví dụ .


Điều này (ví dụ ma trận) là sử dụng đơn giản operator()nhưng không sử dụng các thuộc tính đối tượng hàm.
bỏ

3

Giống như đã được lặp đi lặp lại, functor là các lớp có thể được coi là các hàm (toán tử quá tải ()).

Chúng rất hữu ích cho các tình huống trong đó bạn cần liên kết một số dữ liệu với các cuộc gọi lặp lại hoặc bị trì hoãn đến một chức năng.

Ví dụ, một danh sách liên kết của functor có thể được sử dụng để triển khai hệ thống coroutine đồng bộ trên không cơ bản thấp, bộ điều phối tác vụ hoặc phân tích tệp gián đoạn. Ví dụ:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

Tất nhiên, những ví dụ này không hữu ích cho bản thân họ. Chúng chỉ cho thấy functor có thể hữu ích như thế nào, bản thân functor rất cơ bản và không linh hoạt và điều này làm cho chúng ít hữu ích hơn, ví dụ, những gì boost cung cấp.


2

Functor được sử dụng trong gtkmm để kết nối một số nút GUI với một phương thức hoặc chức năng C ++ thực tế.


Nếu bạn sử dụng thư viện pthread để làm cho ứng dụng của bạn đa luồng, Functor có thể giúp bạn.
Để bắt đầu một luồng, một trong các đối số của pthread_create(..)là con trỏ hàm được thực thi trên luồng của chính nó.
Nhưng có một điều bất tiện. Con trỏ này không thể là con trỏ tới một phương thức, trừ khi đó là phương thức tĩnh hoặc trừ khi bạn chỉ định lớp đó , như thế nào class::method. Và một điều nữa, giao diện của phương thức của bạn chỉ có thể là:

void* method(void* something)

Vì vậy, bạn không thể chạy (một cách rõ ràng đơn giản), các phương thức từ lớp của bạn trong một luồng mà không làm gì thêm.

Một cách rất tốt để xử lý các luồng trong C ++, là tạo Threadlớp của riêng bạn . Nếu bạn muốn chạy các phương thức từ MyClasslớp, điều tôi đã làm là, chuyển đổi các phương thức đó thành Functorcác lớp dẫn xuất.

Ngoài ra, Threadlớp có phương thức này: static void* startThread(void* arg)
Một con trỏ tới phương thức này sẽ được sử dụng làm đối số để gọi pthread_create(..). Và những gì startThread(..)sẽ nhận được trong arg là một void*tham chiếu được truyền tới một thể hiện trong một Functorlớp của bất kỳ lớp dẫn xuất nào , nó sẽ được chuyển trở lại Functor*khi được thực thi, và sau đó được gọi là run()phương thức của nó .


2

Để thêm vào, tôi đã sử dụng các đối tượng hàm để khớp một phương thức kế thừa hiện có với mẫu lệnh; (chỉ nơi mà vẻ đẹp của mô hình OO đúng OCP tôi cảm thấy); Cũng thêm vào đây các mẫu bộ điều hợp chức năng liên quan.

Giả sử phương thức của bạn có chữ ký:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

Chúng ta sẽ thấy làm thế nào chúng ta có thể điều chỉnh nó cho mẫu Lệnh - trước tiên, bạn phải viết một bộ điều hợp chức năng thành viên để nó có thể được gọi là một đối tượng hàm.

Lưu ý - điều này thật tệ và có thể bạn có thể sử dụng trình trợ giúp liên kết Boost, v.v., nhưng nếu bạn không thể hoặc không muốn, đây là một cách.

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

Ngoài ra, chúng ta cần một phương thức trợ giúp mem_fun3 cho lớp trên để hỗ trợ gọi.

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));

}

Bây giờ, để liên kết các tham số, chúng ta phải viết hàm binder. Vì vậy, ở đây nó đi:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

Và, một hàm trợ giúp để sử dụng lớp binder3 - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

Bây giờ, chúng ta phải sử dụng điều này với lớp Command; sử dụng typedef sau:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

Đây là cách bạn gọi nó:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

Lưu ý: f3 (); sẽ gọi phương thức task1-> ThreeParameterTask (21,22,23);.

Toàn bộ bối cảnh của mẫu này tại liên kết sau


2

Một lợi thế lớn của việc thực hiện các chức năng như functor là chúng có thể duy trì và tái sử dụng trạng thái giữa các cuộc gọi. Ví dụ, nhiều thuật toán lập trình động, như thuật toán Wagner-Fischer để tính khoảng cách Levenshtein giữa các chuỗi, hoạt động bằng cách điền vào một bảng kết quả lớn. Việc phân bổ bảng này mỗi khi hàm được gọi là không hiệu quả, do đó, việc thực hiện hàm như một hàm functor và biến bảng thành một biến thành viên có thể cải thiện hiệu năng rất nhiều.

Dưới đây là một ví dụ về việc thực hiện thuật toán Wagner-Fischer dưới dạng functor. Lưu ý cách bảng được phân bổ trong hàm tạo, và sau đó được sử dụng lại operator(), với kích thước thay đổi khi cần thiết.

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

1

Functor cũng có thể được sử dụng để mô phỏng xác định hàm cục bộ trong hàm. Tham khảo câu hỏikhác .

Nhưng một functor cục bộ không thể truy cập bên ngoài các biến tự động. Hàm lambda (C ++ 11) là một giải pháp tốt hơn.


-10

Tôi đã "phát hiện" một cách sử dụng functor rất thú vị: Tôi sử dụng chúng khi tôi không có tên hay cho một phương thức, vì functor là một phương thức không có tên ;-)


Tại sao bạn mô tả một functor là một "phương thức không có tên"?
Anderson Green

5
Một hàm không có tên được gọi là lambda.
Paul Fultz II
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.