2011-12-20 67 views
5

Thành thật mà nói, tôi không thực sự biết, làm thế nào để hỏi câu hỏi này, vì vậy hãy đừng giận :)C++ hàm trong cha mẹ trở lại con

Dù sao, tôi muốn có mutators (setters) trong lớp học của tôi để trả lại this để cho phép jQuery giống như a.name("something").address("somethingelse"); Tôi có một lớp cha mẹ (Entity) và một số lớp con (Client, Agent etc.). Các mutator cho hầu hết mọi thứ được thừa hưởng từ lớp Entity (như tên hoặc địa chỉ), nhưng chúng trả về một đối tượng Entity, vì vậy tôi không thể gọi trình biến đổi khách hàng trên chúng.

Nói cách khác:

// name mutator 
Entity& Entity::name(const string& name) { 
    // [...] checks 
    _name = name; 
    return *this; 
} 

// budgetRange mutator 
Client& Client::budgetRange(const long int& range) { 
    // [...] checks 
    _budgetRange = range; 
    return *this; 
} 

sau đó khi tôi gọi nó là:

Client a; a.name("Dorota Adamczyk").budgetRange(50); 

Trình biên dịch (logic) cho biết, rằng đối tượng Entity không có thành viên budgetRange (vì tên trả về một thực thể, không phải là Khách hàng).

Câu hỏi của tôi bây giờ là: làm cách nào tôi có thể triển khai một cái gì đó như thế này? Tôi nghĩ về quá tải tất cả các chức năng Entity trong childclasses nhưng điều đó sẽ không được tốt đẹp và sẽ chống lại ý tưởng của thừa kế :)

Cảm ơn bạn trước cho ý tưởng của bạn: D

+0

google cũng cho 'phương thức chaining' và' tên tham số idiom', đó là những gì bạn đang làm.Lưu ý rằng, khi kế thừa từ 'Client', mã của bạn sẽ lại phá vỡ bằng cách sử dụng giải pháp CRTP bên dưới. – cheind

Trả lời

7

Bạn nên sử dụng CRTP.

template<class Derived> 
class Entity 
{ 
    Derived* This() { return static_cast<Derived*>(this); } 

public: 
    Derived& name(const string& name) 
    { 
     ... 
     return *This(); 
    } 
}; 

class Client : public Entity<Client> 
{ 
public: 
    Client& budgetRange(const long& range) 
    { 
     ...  
     return *this; 
    } 
}; 

Nếu bạn muốn sử dụng chức năng ảo, bạn cũng có thể thêm lớp cơ sở trừu tượng, như thế này:

class AbstractEntity 
{ 
public: 
    virtual void foo() = 0; 

    virtual ~AbstractEntity(); 
}; 

template<class Derived> 
class Entity : AbstractEntity 
{...}; 
+0

Tôi thực sự thích ý tưởng này (đặc biệt là hàm This() ^^). Cảm ơn rất nhiều, đó là những gì tôi đang tìm kiếm :) – Asmodiel

+0

Công cụ tuyệt vời. Tôi đã tìm kiếm một ví dụ điển hình về CRTP. Cảm ơn. – sje397

+0

@ sje397 một ví dụ điển hình khác về CRTP là một lớp cây chung chung, cung cấp các triển khai nút cụ thể thông qua 'mẫu nút lớp'. Ví dụ: 'D & node :: left()' có thể cung cấp nút implicit cụ thể. Bằng cách đó bạn có thể nhồi nhét tất cả logic cây vào lớp nút chung. – cheind

3

Các "tò mò đệ quy mẫu "mẫu có thể giúp ở đây; làm cho các lớp cơ sở mẫu, parametrised bởi lớp có nguồn gốc, dọc theo dòng:

template <typename Derived> 
struct Entity { 
    Derived & name(std::string const & name) { 
     // stuff 
     return static_cast<Derived&>(*this); 
    } 
}; 

struct Client : Entity<Client> { 
    Client & budget(long range) { 
     // stuff 
     return *this; 
    } 
}; 

Client().name("Mike").budget(50); // should compile 

này sẽ chỉ có tác dụng nếu tất cả các loại của bạn kế thừa trực tiếp từ Entity. Nếu bạn cần các loại đa hình (nghĩa là tất cả chia sẻ một lớp cơ sở chung), thì bạn sẽ cần phải thêm một lớp cơ sở không phải mẫu khác, và có Entity kế thừa từ đó.

+0

Cảm ơn bạn rất nhiều cho bạn câu trả lời, nhưng tôi sẽ chọn Abyx 'một khi ông bao gồm ý tưởng này() :) Upvoted mặc dù. – Asmodiel

2

Bây giờ gần như tất cả mọi thứ đã được nói, tôi muốn thêm một mảnh của câu trả lời cho phép một người sử dụng CRTP qua nhiều cấp độ thừa kế:

Việc triển khai CRTP ở trên bị gián đoạn khi một người muốn kế thừa từ Client, kể từ Derived sẽ tham chiếu đến Client. Trong trường hợp bạn muốn để có thể thực hiện thành ngữ thông số có tên trên nhiều cấp độ thừa kế sử dụng mẫu CRTP, bạn cần phải viết mã lớp học của bạn như vậy

template<class Derived> 
class Entity_T 
{ 
protected: 
    Derived* This() { return static_cast<Derived*>(this); } 
public: 
    Derived& name(const string& name) 
    { 
     ... 
     return *This(); 
    } 
}; 

template<class Derived> 
class Client_T : public Entity_T<Derived> 
{ 
    Derived& budgetRange(const long& range) 
    { 
     ...  
     return *This(); 
    } 
}; 

Để cung cấp cho người dùng một phiên bản mẫu miễn phí của Client_T thêm

class Client : public Client_T<Client> {}; 

Có hay không điều này có giá trị cơ sở mã mở rộng hoàn toàn tùy thuộc vào bạn. Lưu ý rằng tôi chưa biên dịch mã ở trên.

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