2013-09-24 123 views
13

Tôi đang cố gắng quá tải một số chức năng mẫu để thực hiện hành động cụ thể nếu tôi gọi nó bằng cách sử dụng một lớp nhất định MyClass hoặc bất kỳ lớp dẫn xuất MyClassDer. Đây là mã:quá tải chức năng và thừa kế

#include <iostream> 

struct MyClass { 
    virtual void debug() const { 
     std::cerr << "MyClass" << std::endl; 
    }; 
}; 

struct MyClassDer : public MyClass { 
    virtual void debug() const { 
     std::cerr << "MyClassDer" << std::endl; 
    }; 
}; 

template <typename T> void func (const T& t) { 
    std::cerr << "func template" << std::endl; 
} 

void func (const MyClass& myClass) { 
    std::cerr << "func overloaded" << std::endl; 
    myClass.debug(); 
} 


int main(int argc, char **argv) { 
    func (1); 
    MyClass myClass; 
    func (myClass); 
    MyClassDer myClassDer; 
    func (myClassDer); 
} 

Đầu ra là:

func template 
func overloaded 
MyClass 
func template 

func (myClassDer) gọi là mẫu chức năng thay vì void func (const MyClass& myClass). Tôi có thể làm gì để có được hành vi mong đợi?

Cảm ơn

Trả lời

3

Bạn có thể sử dụng SFINAE:

#include <type_traits> 

template <typename T> 
void func (const T& t, typename std::enable_if<!std::is_base_of<MyClass, T>::value>::type * = nullptr) { 
    std::cout << "func template" << std::endl; 
} 

template < 
    typename T 
    , typename = typename std::enable_if<std::is_base_of<MyClass, T>::value>::type 
> 
void func (const T& t) { 
    std::cout << "func overloaded" << std::endl; 
    t.debug(); 
} 

Nếu bạn không có C++ 11, tăng cung cấp các chức năng tương tự.

Live example

EDIT

này nên làm việc mà không C++ 11 (sử dụng tăng):

#include "boost/type_traits.hpp" 

template <typename T> 
void func (const T& t, typename boost::enable_if<!boost::is_base_of<MyClass, T>::value>::type * = 0) { 
    std::cout << "func template" << std::endl; 
} 

template <typename T> 
void func (const T& t, typename boost::enable_if<boost::is_base_of<MyClass, T>::value>::type * = 0) { 
    std::cout << "func overloaded" << std::endl; 
    t.debug(); 
} 
+0

Điều này có vẻ là giải pháp đúng. Tôi sẽ xem cách thực hiện điều đó bằng cách tăng cường. – user2811040

+0

@ user2811040 'boost :: enable_if' và' boost :: is_base_of'. Đó là nó. – Angew

+0

@ user2811040 Và nếu không có C++ 11, hãy di chuyển tham số mẫu thứ hai sang thủ thuật con trỏ. – Angew

0
MyClass *myClassDer = new MyClassDer; 
func(*myClassDer); 
delete myClassDer; 
+1

Bất kỳ lý do cho việc giới thiệu phân bổ động? – Angew

+0

Quá tải chức năng được giải quyết tại thời gian biên dịch, do đó bạn cần 'MyClass' để có thể nhấn đúng chức năng. Nhưng đối với tính đa hình thời gian chạy để làm việc, bạn thực sự cần một đối tượng 'MyClassDer'. Do đó 'mới'. – HAL

+0

sử dụng phân bổ động không thay đổi bất cứ điều gì. Tính đa hình hoạt động tốt với các tham chiếu ... – user2811040

0

Bạn sẽ cần phải sử dụng đa hình để gọi hàm mẫu của bạn. Bạn cần một tham chiếu đến lớp cơ sở của bạn:

int main(int argc, char **argv) { 
    func (1); 
    MyClass myClass; 
    func (myClass); 
    MyClassDer myClassDer; 
    MyClass* mc = &myClassDer; 
    func (*mc); 
} 

More polymorphism examples and details here

+1

Bạn cũng có thể chỉ cần tạo tham chiếu và xóa một vài '*' –

4

Đối với lý do tại sao mã của bạn không làm việc: xem @ lời giải thích tuyệt vời của David. Để có được nó để làm việc, bạn có thể sử dụng SFINAE ("Substition Thất bại không phải là một Errro) bằng cách thêm một tham số mẫu ẩn Requires (tên chỉ dành cho mục đích tài liệu)

template < 
    typename T, typename Requires = typename 
    std::enable_if<!std::is_base_of<MyClass, T>::value, void>::type 
> 
void func (const T& t) { 
    std::cerr << "func template" << std::endl; 
} 

này sẽ vô hiệu hóa mẫu này để giải quyết tình trạng quá tải bất cứ khi nào T bằng hoặc có nguồn gốc từ MyClass và sẽ chọn chức năng thông thường thay thế (chuyển đổi từ gốc sang cơ sở sẽ được thực hiện, trái ngược với khấu trừ đối số mẫu, chỉ xem xét đối sánh chính xác). điều này và thêm một số tình trạng quá tải với các điều kiện không chồng chéo bên trong các std::enable_if để có một sự lựa chọn chi tiết các quá tải chức năng sẽ được xem xét.Nhưng hãy cẩn thận, SFINAE là tinh tế!

Live Example.

Lưu ý: Tôi đã viết SFINAE với cú pháp C++ 11, sử dụng thông số mẫu mặc định cho mẫu chức năng. Trong C++ 98, bạn cần phải thêm tham số mặc định thông thường hoặc sửa đổi kiểu trả về.

+0

Tuyệt vời !!!!!! – shofee

0

của nó vì chữ ký chức năng quá tải của bạn là,

void func (const MyClass& myClass) 
{ 
    std::cerr << "func overloaded" << std::endl; 
    myClass.debug(); 
} 

tức là nó muốn MyClass như tham số của nó và bạn đang gọi nó bằng cách sử MyClassDer. Vì vậy, tại thời gian biên dịch nó giải quyết các chức năng quá tải khác và liên kết với điều đó. Như các chức năng khác là templated không có vấn đề cho trình biên dịch để liên kết với điều đó.

Vì vậy, nếu bạn muốn chuyển đối tượng MyClassDer, bạn vẫn có thể làm điều đó bằng cách sử dụng đa hình.

MyClass *myClassDer = new MyClassDer; 
func(*myClassDer); 
+4

Không cần phải 'mới' bất cứ điều gì, bạn có thể tạo con trỏ đến một biến địa phương (hoặc thậm chí tốt hơn, một tham chiếu) –

1

Polymorphism xảy ra trong thời gian chạy, nhưng lựa chọn một chức năng quá tải xảy ra ở thời gian biên dịch.

Vì vậy, trong thời gian biên dịch quá tải tốt nhất để chấp nhận MyClassDer

func<MyClassDer> (const MyClassDer& t) 

hơn

func<MyClass> (const MyClass& t) 

sau đó biên dịch chọn đầu tiên.


Một khả năng để giải quyết vấn đề này là:

func(static_cast<MyClass&>(myClassDer)); 
11

Đây chỉ là cách giải quyết tình trạng quá tải hoạt động. Khi tra cứu hoàn thành nó tìm thấy cả mẫu và chức năng. Các kiểu mẫu sau đó được suy ra và độ phân giải quá tải bắt đầu. Trong trường hợp của một đối số kiểu MyClass hai candiates là:

void func<MyClass>(MyClass const&); 
void func(MyClass const&); 

nào là bình đẳng trận đấu tốt cho các đối số, nhưng thứ hai là một tổ chức phi mẫu được ưa thích. Trong trường hợp của MyClassDer:

void func<MyClassDer>(MyClassDer const&); 
void func(MyClass const&); 

Trong trường hợp này là người đầu tiên là một ứng cử viên tốt hơn so với một thứ hai, như một thứ hai đòi hỏi một sự chuyển đổi có nguồn gốc-to-base và được nhặt.

Có các cách tiếp cận khác nhau để gửi trực tiếp đến mã của bạn. Cách đơn giản nhất chỉ là ép buộc kiểu của đối số là MyClass và vì thế dự phòng cho trường hợp ban đầu:

func(static_cast<MyClass&>(myClassDer)); 

Trong khi đơn giản, điều này cần phải được thực hiện ở khắp mọi nơi và nếu bạn quên chỉ trong một nơi, điều sai sẽ được gọi là. Phần còn lại của giải pháp là phức tạp và bạn có thể muốn xem xét liệu sẽ không tốt hơn nếu chỉ cung cấp các tên hàm khác nhau.

Một trong những tùy chọn được sử dụng SFINAE để vô hiệu hóa các mẫu khi các loại có nguồn gốc từ MyClass:

template <typename T> 
typename std::enable_if<!std::is_base_of<MyClass,MyClassDer>::value>::type 
func(T const & t) { ... } 

Trong trường hợp này, sau khi tra cứu, trình biên dịch sẽ thực hiện loại trừ, và nó sẽ suy T để là MyClassDer, sau đó nó sẽ đánh giá kiểu trả về của hàm (SFINAE cũng có thể được áp dụng cho một đối số mẫu hoặc hàm khác). is_base_of sẽ mang lại falseenable_if sẽ không có loại lồng nhau. Khai báo hàm sẽ không đúng định dạng và trình biên dịch sẽ thả nó, để lại độ phân giải được thiết lập với một ứng cử viên duy nhất, quá tải không mẫu.

Tùy chọn khác sẽ cung cấp giao diện mẫu đơn và gửi nội bộ đến mẫu hoặc quá tải (theo tên khác) bằng cách sử dụng công cụ gắn thẻ. Ý tưởng là tương tự, bạn đánh giá đặc điểm bên trong khuôn mẫu và gọi hàm với một kiểu được tạo ra từ đánh giá đó.

template <typename T> 
void func_impl(T const&, std::false_type) {...} 
void func_impl(MyClass const&, std::true_type) {...} 

template <typename T> 
void func(T const &x) { 
    func_impl(x,std::is_base_of<MyClass,MyClassDer>::type()); 
} 

Có các lựa chọn thay thế khác, nhưng đó là hai lựa chọn thay thế phổ biến và phần còn lại chủ yếu dựa trên cùng nguyên tắc.

Một lần nữa, hãy cân nhắc xem vấn đề có đáng giá tính phức tạp của giải pháp hay không. Trừ khi các cuộc gọi đến func được thực hiện bên trong mã chung, một sự thay đổi đơn giản của tên hàm sẽ giải quyết vấn đề mà không cần thêm sự phức tạp mà bạn hoặc các nhà duy trì khác có thể gặp vấn đề.

+2

Bạn đã gõ câu trả lời rất lớn này chỉ trong vài giây? +1 – deepmax

+0

@MM .: Giống như một vài phút nữa ... Tôi không phải là người phá sản chậm, nhưng tôi không phải là Flash hoặc là –

+0

@MM. Chúa Ba Ngôi của Tra cứu Tên, Khấu trừ đối số và Độ phân giải quá tải xuất hiện thường xuyên đến mức phải có nhiều hay ít bộ nhớ làm việc này khi viết mã mẫu. Xem ví dụ Bài giảng của Stephan T. Lavavej về [Core C++] (http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-) – TemplateRex

0

Chỉ cần quăng nó vào loại hình cơ bản:

MyClassDer myClassDer; 
func(static_cast<MyClass&>(myClassDer)); 
+0

static_cast không phải là giải pháp tốt cho tôi vì tôi thực sự không thể mong đợi người dùng chức năng của tôi sử dụng cấu trúc này. Hơn nữa chức năng của tôi sẽ thực sự là một nhà điều hành! – user2811040

+0

@ user2811040 Ah. Và tôi cho rằng bạn muốn người dùng của hàm của bạn có thể tự mở rộng MyClass.Trong trường hợp đó, bạn dường như đã chấp nhận câu trả lời đúng. –