2017-03-12 31 views
5

Gần đây, tôi đã đọc cuốn sách Hiệu lực C++ và có tuyên bố về typedef trong Khoản 35 gây nhầm lẫn cho tôi.Cách hiểu typedef trong khai báo này

class GameCharacter; // Question1: Why use forward declaration? 

int defaultHealthCalc(const GameCharacter& gc); 

class GameCharacter{ 

    public: 
     typedef int (*HealthCalcFunc)(const GameCharacter&); // Question 2: What does this mean? 

     explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) 
      : healthFunc(hcf) 
     {} 

     int healthValue() const 
     {return healthFunc(*this); } 

    private: 
     HealthCalcFunc healthFunc; 
}; 

Vì vậy, câu hỏi đầu tiên của tôi là: Tại sao tác giả sử dụng tờ khai chuyển tiếp ở đây? Có lý do cụ thể nào không?

Và câu hỏi thứ hai của tôi là: Làm cách nào để hiểu khai báo typedef và cách sử dụng? Tôi chỉ biết một cái gì đó giống như typedef int MyInt;

Trả lời

8

Vì vậy, câu hỏi đầu tiên của tôi là tại sao tác giả sử dụng phía trước tuyên bố đây?

Để trình biên dịch biết rằng GameCharacter là tên hợp lệ khi gặp phải dòng int defaultHealthCalc(const GameCharacter& gc);.

Và câu hỏi thứ hai của tôi là cách hiểu khai báo typedef và cách sử dụng?

Lý tưởng nhất, bạn hãy không sử dụng nữa.

Bắt đầu với C++ 11, using là giải pháp thay thế cao cấp vì nó dễ đọc hơn; không giống như typedef 'con trỏ chức năng được chỉnh sửa, nó sẽ tách biệt tên với tên mô tả. Hãy so sánh này:

typedef int (*HealthCalcFunc)(const GameCharacter&); 

Với điều này:

using HealthCalcFunc = int (*)(const GameCharacter&); 

Trong phiên bản typedef, tên HealthCalcFunc được bao quanh trên cả hai mặt bằng đó mà tên mô tả. Điều này làm tổn thương khả năng đọc.


Nhưng mã vẫn có thể được cải thiện, bởi vì C++ 11 cũng giới thiệu std::function như một sự thay thế và/hoặc một lớp trừu tượng trên con trỏ hàm.

using HealthCalcFunc = std::function<int(const GameCharacter&)>; 

Điều này rất dễ đọc, hầu như không phải giải thích chút nào. HealthCalcFunc là hàm trả về số int và mất const GameCharacter&.

Chức năng defaultHealthCalc phù hợp với định nghĩa này. Dưới đây là ví dụ đầy đủ:

#include <functional> 

class GameCharacter; 

int defaultHealthCalc(const GameCharacter& gc); 

class GameCharacter { 
public: 
     using HealthCalcFunc = std::function<int(const GameCharacter&)>; 

     explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) 
     : healthFunc(hcf) 
     {} 
     int healthValue() const 
     {return healthFunc(*this); } 
private: 
     HealthCalcFunc healthFunc; 
}; 

Điều tuyệt vời về std::function là bạn không bị giới hạn chức năng tự do. Bạn có thể truyền mọi thứ giống như chức năng. Dưới đây là một số ví dụ:

struct Functor 
{ 
    int f(const GameCharacter& gc); 
}; 

int main() 
{ 
    // normal function: 
    GameCharacter character1(defaultHealthCalc); 

    // lambda: 
    GameCharacter character2([](auto const& gc) { return 123; }); 

    // using std::bind: 
    Functor functor; 
    using namespace std::placeholders; 
    GameCharacter character3(std::bind(&Functor::f, functor, _1)); 
} 

Xem thêm Should I use std::function or a function pointer in C++?.

+0

_ "Vì mã vẫn có thể được ** cải thiện **" _: đề cập đến 'std :: function' ở đây là thích hợp, nhưng nó là một sự thay thế hoàn toàn khác, và cho một ví dụ đơn giản như thế này, nó có thể được đề cập đến những cải tiến khi chuyển từ loại con trỏ hàm trọng lượng nhẹ sang loại hoàn toàn khác và so với sinh vật "nặng" của 'std :: function'. Cả hai đều có vị trí của chúng (chủ yếu là, imo, 'std :: function' nên được ưu tiên, nhưng khi làm việc với các API cũ hơn ...), nhưng là các khái niệm rất khác nhau, và có nên sử dụng một khái niệm khác hay không? . – dfri

+0

@dfri: Tôi nghĩ rằng 'std :: function' là đơn giản hơn nhiều để viết, đọc và áp dụng hơn một con trỏ hàm, vì vậy tôi xem nó là giải pháp tiêu chuẩn, trong khi con trỏ hàm là những con thú có mục đích đặc biệt. –

+0

Có, tôi đồng ý, lưu ý chỉnh sửa ninja của tôi.Tuy nhiên, khi đề cập đến 'std :: function' như là một sự cải tiến luôn luôn, nó có thể có giá trị để chỉ ra rằng có những tình huống mà một con trỏ hàm đơn giản là thích (hoặc thậm chí là thay thế duy nhất). Dù sao, câu trả lời tốt, chỉ là một đường nhỏ bên cạnh của tôi ở trên! – dfri

9
typedef int (*HealthCalcFunc)(const GameCharacter&); 

Khai báo một typedef tên HealthCalcFunc cho một loại con trỏ hàm, nơi chữ ký chức năng trả về một int và mất một tham số của const GameCharacter&.

Tuyên bố chuyển tiếp là cần thiết vì lớp đó được sử dụng làm loại thông số trong typedef.

11

Tuyên bố chuyển tiếp là cần thiết vì khai báo chuyển tiếp defaultHealthCalc, sử dụng GameCharacter. Không khai báo trước, trình biên dịch sẽ không biết rằng sau này, sẽ có một kiểu có tên là GameCharacter, và vì vậy bạn cần chuyển tiếp khai báo nó, hoặc di chuyển định nghĩa trước khi khai báo hàm.

Điều đó typedef có nghĩa là đặt tên loại int(*)(const GameCharacter&)HealthCalcFunc. Đó là con trỏ hàm tới một số int(const GameCharacter&). Vì lý do đó, typedefs được khá không đọc được, nó được khuyên nên sử dụng một tuyên bố using thay vì:

using HealthCalcFunc = int(*)(const GameCharacter&); 
13

Vì vậy, câu hỏi đầu tiên của tôi là tại sao tác giả sử dụng phía trước tuyên bố đây?

Bởi vì việc khai báo của hàm defaultHealthCalc sử dụng const GameCharacter& như kiểu của tham số, sau đó GameCharacter cần phải được khai báo trước.

Và câu hỏi thứ hai của tôi là làm thế nào để hiểu được khai typedef

Nó tuyên bố một loại tên HealthCalcFunc, mà là một loại con trỏ đến chức năng, trong đó có const GameCharacter& như tham số và trả về int.

và cách sử dụng?

Cũng giống như các mẫu mã cho thấy,

explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) // declare a parameter with type HealthCalcFunc; 
                   // the default argument is function pointer to defaultHealthCalc 
: healthFunc(hcf)            // initialize healthFunc with hcf 
{} 

int healthValue() const 
{return healthFunc(*this); }         // invoke the function pointed by healthFunc 

HealthCalcFunc healthFunc;          // declare a member with type HealthCalcFunc 
2

Tuyên bố về phía trước là GameCharacter là không cần thiết; tuyên bố chức năng có thể đã đọc:

int defaultHealthCalc(const class GameCharacter& gc); 

có tác dụng tương tự như mã gốc. Nhưng nó được coi là dễ đọc hơn để có tuyên bố về phía trước trên dòng riêng của nó.

Các vấn đề liên quan