Một mẫu chức năng thành viên lớp có thể là ảo?


304

Tôi đã nghe nói rằng các mẫu hàm thành viên lớp C ++ không thể là ảo. Điều này có đúng không?

Nếu chúng có thể là ảo, ví dụ về một kịch bản trong đó một người sẽ sử dụng một chức năng như vậy là gì?


12
Tôi đã phải đối mặt với một vấn đề tương tự, và cũng học được rằng nó là tranh cãi và ảo cùng một lúc. Giải pháp của tôi là viết ma thuật mẫu sẽ phổ biến giữa các lớp dẫn xuất và gọi một hàm ảo thuần túy là phần chuyên biệt. Tất nhiên điều này liên quan đến bản chất vấn đề của tôi, vì vậy có thể không hoạt động trong mọi trường hợp.
Tamás Szelei

Câu trả lời:


329

Các mẫu là tất cả về mã tạo trình biên dịch tại thời gian biên dịch . Các chức năng ảo là tất cả về hệ thống thời gian chạy để tìm ra chức năng nào sẽ gọi vào thời gian chạy .

Khi hệ thống thời gian chạy đã tìm ra nó sẽ cần gọi một hàm ảo templatized, quá trình biên dịch hoàn tất và trình biên dịch không thể tạo ra thể hiện thích hợp nữa. Do đó, bạn không thể có các mẫu chức năng thành viên ảo.

Tuy nhiên, có một vài kỹ thuật mạnh mẽ và thú vị bắt nguồn từ việc kết hợp đa hình và các mẫu, đáng chú ý là cái gọi là xóa kiểu .


32
Tôi không thấy một lý do ngôn ngữ cho việc này, chỉ có lý do thực hiện . vtables không phải là một phần của ngôn ngữ - chỉ là cách trình biên dịch chuẩn thực hiện ngôn ngữ.
gerardw

16
Virtual functions are all about the run-time system figuring out which function to call at run-time- xin lỗi nhưng đây là một cách khá sai lầm và khá khó hiểu. Nó chỉ là một sự gián tiếp và không có "thời gian chạy" liên quan, trong thời gian biên dịch hàm được gọi là hàm được trỏ bởi con trỏ thứ n trong vtable. "Tìm ra" ngụ ý có loại kiểm tra và như vậy, đó không phải là trường hợp. Once the run-time system figured out it would need to call a templatized virtual function- có hay không chức năng là ảo được biết tại thời điểm biên dịch.
dtech

9
@ddriver: 1. Nếu trình biên dịch nhìn thấy void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }, thì nó "biết" hàm nào được gọi tại điểm cb.f()được gọi và không biết điều đó cho vb.f(). Cái sau phải được tìm ra trong thời gian chạy , bởi hệ thống thời gian chạy . Cho dù bạn muốn gọi đây là "tìm hiểu" và liệu điều này có hiệu quả hơn hay ít hơn, không thay đổi những sự thật này một chút.
sbi

9
@ddriver: 2. Các mẫu của hàm (thành viên) là các hàm (thành viên), vì vậy không có vấn đề gì với việc đưa một con trỏ tới một thể hiện như vậy vào vtable. Nhưng những trường hợp mẫu nào là cần thiết chỉ được biết khi người gọi được biên dịch, trong khi vtables được thiết lập khi lớp cơ sở và lớp dẫn xuất được biên dịch. Và tất cả đều được biên dịch riêng. Thậm chí tệ hơn - các lớp dẫn xuất mới có thể được liên kết thành các hệ thống đang chạy trong thời gian chạy (nghĩ rằng trình duyệt của bạn đang tải một plugin một cách linh hoạt). Ngay cả mã nguồn của người gọi cũng có thể bị mất khi lớp dẫn xuất mới được tạo.
sbi

9
@sbi: Tại sao bạn lại đưa ra giả định dựa trên tên của tôi? Tôi không nhầm lẫn thuốc generic và mẫu. Tôi biết rằng các tổng quát của Java hoàn toàn là thời gian. Bạn đã không giải thích cặn kẽ tại sao bạn không thể có các mẫu hàm thành viên ảo trong C ++, nhưng InQsitive thì có. Bạn đã đơn giản hóa quá mức mẫu và cơ chế ảo để 'biên dịch thời gian' so với 'thời gian chạy' và kết luận rằng "bạn không thể có các mẫu hàm thành viên ảo". Tôi đã tham khảo câu trả lời của InQsitive, trong đó tham khảo "Mẫu C ++ Hướng dẫn hoàn chỉnh". Tôi không coi đó là "vẫy tay". Chúc một ngày tốt lành.
Javanator

133

Từ mẫu C ++ Hướng dẫn đầy đủ:

Mẫu hàm thành viên không thể được khai báo ảo. Ràng buộc này được áp đặt vì việc triển khai thông thường của cơ chế gọi hàm ảo sử dụng bảng có kích thước cố định với một mục nhập cho mỗi hàm ảo. Tuy nhiên, số lần khởi tạo của mẫu hàm thành viên không được sửa cho đến khi toàn bộ chương trình được dịch. Do đó, việc hỗ trợ các mẫu hàm thành viên ảo sẽ yêu cầu hỗ trợ cho một loại cơ chế hoàn toàn mới trong trình biên dịch và trình liên kết C ++. Ngược lại, các thành viên bình thường của các mẫu lớp có thể là ảo vì số của chúng được cố định khi một lớp được khởi tạo


8
Tôi nghĩ rằng trình biên dịch và trình liên kết C ++ ngày nay, đáng chú ý là có hỗ trợ tối ưu hóa thời gian liên kết, có thể tạo ra các vtables và offset cần thiết tại thời điểm liên kết. Vậy có lẽ chúng ta sẽ có được tính năng này trong C ++ 2b?
Kai Petzke

33

C ++ không cho phép các chức năng thành viên mẫu ảo ngay bây giờ. Lý do rất có thể là sự phức tạp của việc thực hiện nó. Rajendra đưa ra lý do chính đáng tại sao nó không thể được thực hiện ngay bây giờ nhưng điều đó có thể với những thay đổi hợp lý của tiêu chuẩn. Đặc biệt là tìm ra có bao nhiêu tức thời của một hàm templated thực sự tồn tại và xây dựng vtable có vẻ khó khăn nếu bạn xem xét vị trí của lệnh gọi hàm ảo. Các tiêu chuẩn mọi người chỉ có rất nhiều thứ khác để làm ngay bây giờ và C ++ 1x là rất nhiều công việc cho các nhà văn trình biên dịch là tốt.

Khi nào bạn cần một chức năng thành viên templated? Có lần tôi đã gặp một tình huống như vậy khi tôi cố gắng cấu trúc lại một hệ thống phân cấp với một lớp cơ sở ảo thuần túy. Đó là một phong cách kém để thực hiện các chiến lược khác nhau. Tôi muốn thay đổi đối số của một trong các hàm ảo thành một kiểu số và thay vì làm quá tải hàm thành viên và ghi đè mọi quá tải trong tất cả các lớp con tôi đã cố gắng sử dụng các hàm mẫu ảo (và phải tìm ra chúng không tồn tại .)


5
@pmr: Một hàm ảo có thể được gọi từ mã thậm chí không tồn tại khi hàm được biên dịch. Trình biên dịch sẽ xác định trường hợp nào của hàm thành viên mẫu ảo (lý thuyết) để tạo mã không tồn tại?
sbi

2
@sbi: Vâng, biên dịch riêng biệt sẽ là một vấn đề lớn. Tôi hoàn toàn không phải là chuyên gia về trình biên dịch C ++ nên tôi không thể đưa ra giải pháp. Như với các hàm templated nói chung, nó sẽ được khởi tạo lại trong mọi đơn vị biên dịch, phải không? Điều đó sẽ không giải quyết vấn đề?
pmr

2
@sbi nếu bạn đang đề cập đến các thư viện tải động, đó là một vấn đề chung với các lớp / hàm mẫu, không chỉ với các phương thức mẫu ảo.
Sồi

"C ++ không cho phép [...]" - sẽ đánh giá cao khi xem tham chiếu đến tiêu chuẩn (bất kể là câu trả lời được cập nhật khi câu trả lời được viết hoặc đến ngày tám năm sau) ...
Aconcagua

19

Bảng chức năng ảo

Hãy bắt đầu với một số nền tảng trên các bảng chức năng ảo và cách chúng hoạt động ( nguồn ):

[20.3] Sự khác biệt giữa cách các hàm thành viên ảo và không ảo được gọi là gì?

Các chức năng thành viên không ảo được giải quyết tĩnh. Nghĩa là, hàm thành viên được chọn tĩnh (tại thời gian biên dịch) dựa trên loại con trỏ (hoặc tham chiếu) cho đối tượng.

Ngược lại, các chức năng thành viên ảo được giải quyết linh hoạt (tại thời gian chạy). Nghĩa là, hàm thành viên được chọn động (tại thời gian chạy) dựa trên loại đối tượng, không phải loại con trỏ / tham chiếu đến đối tượng đó. Điều này được gọi là "ràng buộc động." Hầu hết các trình biên dịch sử dụng một số biến thể của kỹ thuật sau: nếu đối tượng có một hoặc nhiều hàm ảo, trình biên dịch sẽ đặt một con trỏ ẩn trong đối tượng được gọi là "con trỏ ảo" hoặc "con trỏ v". Con trỏ v này trỏ đến một bảng toàn cầu được gọi là "bảng ảo" hoặc "bảng v."

Trình biên dịch tạo một bảng v cho mỗi lớp có ít nhất một hàm ảo. Ví dụ: nếu Vòng tròn lớp có các hàm ảo để vẽ () và di chuyển () và thay đổi kích thước (), sẽ có chính xác một bảng v được liên kết với Vòng tròn lớp, ngay cả khi có một đối tượng Vòng tròn gazillion và con trỏ v của mỗi đối tượng Circle sẽ trỏ đến bảng v Circle. Bản thân bảng v có con trỏ tới từng hàm ảo trong lớp. Ví dụ: bảng Circle v sẽ có ba con trỏ: một con trỏ tới Circle :: draw (), một con trỏ tới Circle :: move () và một con trỏ tới Circle :: resize ().

Trong quá trình gửi một hàm ảo, hệ thống thời gian chạy theo con trỏ v của đối tượng đến bảng v của lớp, sau đó theo khe thích hợp trong bảng v đến mã phương thức.

Chi phí không gian chi phí của kỹ thuật trên là danh nghĩa: một con trỏ phụ cho mỗi đối tượng (nhưng chỉ đối với các đối tượng sẽ cần thực hiện liên kết động), cộng với một con trỏ phụ cho mỗi phương thức (nhưng chỉ cho các phương thức ảo). Chi phí thời gian cũng khá thấp: so với một cuộc gọi hàm thông thường, một cuộc gọi hàm ảo yêu cầu hai lần tìm nạp thêm (một để lấy giá trị của con trỏ v, một giây để lấy địa chỉ của phương thức). Không có hoạt động thời gian chạy này xảy ra với các hàm không ảo, vì trình biên dịch giải quyết các hàm không ảo chỉ dành riêng cho thời gian biên dịch dựa trên loại con trỏ.


Vấn đề của tôi, hoặc làm thế nào tôi đến đây

Bây giờ tôi đang cố gắng sử dụng một cái gì đó như thế này cho một lớp cơ sở hình khối với các hàm tải được tối ưu hóa theo khuôn mẫu sẽ được triển khai khác nhau cho các loại hình khối khác nhau (một số được lưu trữ bằng pixel, một số bằng hình ảnh, v.v.).

Một số mã:

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

Những gì tôi muốn là nó, nhưng nó sẽ không được biên dịch do một kết hợp templated ảo:

template<class T>
    virtual void  LoadCube(UtpBipCube<T> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

Tôi đã kết thúc việc di chuyển khai báo mẫu đến cấp độ lớp . Giải pháp này sẽ buộc các chương trình phải biết về các loại dữ liệu cụ thể mà họ sẽ đọc trước khi đọc chúng, điều này không thể chấp nhận được.

Giải pháp

cảnh báo, điều này không đẹp lắm nhưng nó cho phép tôi loại bỏ mã thực thi lặp đi lặp lại

1) trong lớp cơ sở

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

2) và trong các lớp con

void  LoadCube(UtpBipCube<float> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

template<class T>
void  LoadAnyCube(UtpBipCube<T> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1);

Lưu ý rằng LoadAnyCube không được khai báo trong lớp cơ sở.


Đây là một câu trả lời tràn ngăn xếp khác với một công việc xung quanh: cần một cách giải quyết thành viên mẫu ảo .


1
Tôi đã gặp tình huống tương tự, và cấu trúc thừa kế của các lớp đại chúng. macro đã giúp.
ZFY

16

Đoạn mã sau có thể được biên dịch và chạy đúng cách, sử dụng MinGW G ++ 3.4.5 trên Window 7:

#include <iostream>
#include <string>

using namespace std;

template <typename T>
class A{
public:
    virtual void func1(const T& p)
    {
        cout<<"A:"<<p<<endl;
    }
};

template <typename T>
class B
: public A<T>
{
public:
    virtual void func1(const T& p)
    {
        cout<<"A<--B:"<<p<<endl;
    }
};

int main(int argc, char** argv)
{
    A<string> a;
    B<int> b;
    B<string> c;

    A<string>* p = &a;
    p->func1("A<string> a");
    p = dynamic_cast<A<string>*>(&c);
    p->func1("B<string> c");
    B<int>* q = &b;
    q->func1(3);
}

và đầu ra là:

A:A<string> a
A<--B:B<string> c
A<--B:3

Và sau này tôi đã thêm một lớp X mới:

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

Khi tôi cố gắng sử dụng lớp X trong hàm main () như thế này:

X x;
x.func2<string>("X x");

g ++ báo cáo lỗi sau:

vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'

Vì vậy, rõ ràng là:

  • chức năng thành viên ảo có thể được sử dụng trong một mẫu lớp. Trình biên dịch dễ dàng xây dựng vtable
  • Không thể định nghĩa một hàm thành viên mẫu lớp là ảo, như bạn có thể thấy, thật khó để xác định chữ ký hàm và phân bổ các mục vtable.

19
Một mẫu lớp có thể có các chức năng thành viên ảo. Hàm thành viên có thể không phải là cả mẫu hàm thành viên và hàm thành viên ảo.
James McNellis

1
nó thực sự thất bại với gcc 4.4.3. Trên hệ thống của tôi chắc chắn Ubuntu 10.04
blueskin

3
Điều này là hoàn toàn khác với những gì câu hỏi. Ở đây toàn bộ lớp cơ sở được templated. Tôi đã biên soạn loại điều này trước đây. Điều này cũng sẽ được biên dịch trên Visual Studio 2010
DS-bos-msk

14

Không, họ không thể. Nhưng:

template<typename T>
class Foo {
public:
  template<typename P>
  void f(const P& p) {
    ((T*)this)->f<P>(p);
  }
};

class Bar : public Foo<Bar> {
public:
  template<typename P>
  void f(const P& p) {
    std::cout << p << std::endl;
  }
};

int main() {
  Bar bar;

  Bar *pbar = &bar;
  pbar -> f(1);

  Foo<Bar> *pfoo = &bar;
  pfoo -> f(1);
};

có nhiều tác dụng tương tự nếu tất cả những gì bạn muốn làm là có một giao diện chung và trì hoãn thực hiện cho các lớp con.


3
Điều này được gọi là CRTP nếu bất cứ ai tò mò.
Michael Choi

1
Nhưng điều này không giúp ích gì cho các trường hợp, trong đó người ta có một hệ thống phân cấp lớp và muốn có thể gọi các phương thức ảo của con trỏ tới các lớp cơ sở. Con Footrỏ của bạn đủ điều kiện là Foo<Bar>, nó không thể trỏ đến một Foo<Barf>hoặc Foo<XXX>.
Kai Petzke

@KaiPetzke: Bạn không thể xây dựng một con trỏ không bị giới hạn, không. Nhưng bạn có thể tạo mẫu bất kỳ mã nào không cần biết loại cụ thể, có tác dụng tương tự (ít nhất là về mặt khái niệm - rõ ràng là thực hiện hoàn toàn khác nhau).
Tom

8

Không, các chức năng thành viên mẫu không thể là ảo.


9
Sự tò mò của tôi là: Tại sao? Trình biên dịch phải đối mặt với những vấn đề gì khi làm như vậy?
WannaBeGeek

1
Bạn cần một khai báo trong phạm vi (ít nhất, để có được các loại chính xác). Tiêu chuẩn (và ngôn ngữ) được yêu cầu phải có một khai báo trong phạm vi cho các định danh bạn sử dụng.
dirkgently

4

Trong các câu trả lời khác, chức năng mẫu được đề xuất là một mặt tiền và không cung cấp bất kỳ lợi ích thiết thực nào.

  • Các hàm mẫu rất hữu ích để viết mã chỉ một lần bằng các loại khác nhau.
  • Các hàm ảo rất hữu ích để có một giao diện chung cho các lớp khác nhau.

Ngôn ngữ không cho phép các chức năng mẫu ảo nhưng với cách giải quyết, có thể có cả hai, ví dụ: một triển khai mẫu cho mỗi lớp và giao diện chung ảo.

Tuy nhiên, cần xác định cho mỗi kiểu kết hợp một hàm bao bọc ảo giả:

#include <memory>
#include <iostream>
#include <iomanip>

//---------------------------------------------
// Abstract class with virtual functions
class Geometry {
public:
    virtual void getArea(float &area) = 0;
    virtual void getArea(long double &area) = 0;
};

//---------------------------------------------
// Square
class Square : public Geometry {
public:
    float size {1};

    // virtual wrapper functions call template function for square
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for squares
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(size * size);
    }
};

//---------------------------------------------
// Circle
class Circle : public Geometry  {
public:
    float radius {1};

    // virtual wrapper functions call template function for circle
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for Circles
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(radius * radius * 3.1415926535897932385L);
    }
};


//---------------------------------------------
// Main
int main()
{
    // get area of square using template based function T=float
    std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
    float areaSquare;
    geometry->getArea(areaSquare);

    // get area of circle using template based function T=long double
    geometry = std::make_unique<Circle>();
    long double areaCircle;
    geometry->getArea(areaCircle);

    std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
    return 0;
}

Đầu ra:

Diện tích hình vuông là 1, diện tích hình tròn là 3,1415926535897932385

Hãy thử nó ở đây


3

Để trả lời phần thứ hai của câu hỏi:

Nếu chúng có thể là ảo, ví dụ về một kịch bản trong đó một người sẽ sử dụng một chức năng như vậy là gì?

Đây không phải là một điều vô lý muốn làm. Chẳng hạn, Java (trong đó mọi phương thức đều là ảo) không có vấn đề gì với các phương thức chung.

Một ví dụ trong C ++ muốn có một mẫu hàm ảo là một hàm thành viên chấp nhận một trình vòng lặp chung. Hoặc một hàm thành viên chấp nhận một đối tượng hàm chung.

Giải pháp cho vấn đề này là sử dụng kiểu xóa với hàm boost :: any_range và boost ::, điều này sẽ cho phép bạn chấp nhận một trình lặp chung hoặc functor mà không cần phải tạo hàm của bạn thành mẫu.


6
Java generic là cú pháp đường để đúc. Chúng không giống như mẫu.
Brice M. Dempsey

2
@ BriceM.Dempsey: Bạn có thể nói rằng việc truyền vai là cách Java thực hiện các khái quát, chứ không phải là cách khác ... và về mặt ngữ nghĩa, các biểu hiện exclipy trong trường hợp sử dụng là IMO hợp lệ.
einpoklum

2

Có một cách giải quyết cho 'phương thức mẫu ảo' nếu biết trước các kiểu cho phương thức mẫu.

Để hiển thị ý tưởng, trong ví dụ dưới đây chỉ có hai loại được sử dụng ( intdouble).

Ở đó, một phương thức mẫu 'ảo' ( Base::Method) gọi phương thức ảo tương ứng (một trong số Base::VMethod), đến lượt nó, gọi phương thức thực hiện phương thức mẫu ( Impl::TMethod).

Người ta chỉ cần thực hiện phương thức mẫu TMethodtrong các triển khai dẫn xuất ( AImpl, BImpl) và sử dụng Derived<*Impl>.

class Base
{
public:
    virtual ~Base()
    {
    }

    template <typename T>
    T Method(T t)
    {
        return VMethod(t);
    }

private:
    virtual int VMethod(int t) = 0;
    virtual double VMethod(double t) = 0;
};

template <class Impl>
class Derived : public Impl
{
public:
    template <class... TArgs>
    Derived(TArgs&&... args)
        : Impl(std::forward<TArgs>(args)...)
    {
    }

private:
    int VMethod(int t) final
    {
        return Impl::TMethod(t);
    }

    double VMethod(double t) final
    {
        return Impl::TMethod(t);
    }
};

class AImpl : public Base
{
protected:
    AImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t - i;
    }

private:
    int i;
};

using A = Derived<AImpl>;

class BImpl : public Base
{
protected:
    BImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t + i;
    }

private:
    int i;
};

using B = Derived<BImpl>;

int main(int argc, const char* argv[])
{
    A a(1);
    B b(1);
    Base* base = nullptr;

    base = &a;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;

    base = &b;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;
}

Đầu ra:

0
1
2
3

NB: Base::Methodthực sự là dư thừa cho mã thực ( VMethodcó thể được công khai và sử dụng trực tiếp). Tôi đã thêm nó để nó trông giống như một phương thức mẫu 'ảo' thực tế.


Tôi đã đưa ra giải pháp này trong khi giải quyết một vấn đề trong công việc. Nó có vẻ tương tự như của Mark Essel ở trên, nhưng, tôi hy vọng, được thực hiện và giải thích tốt hơn.
sad1raf

Tôi đã đủ điều kiện này là mã obfuscation và bạn vẫn không hiểu được thực tế là bạn phải sửa đổi Baselớp gốc mỗi khi bạn cần gọi một hàm mẫu với loại đối số không tương thích với các đối số được triển khai cho đến nay. Tránh sự cần thiết này là ý định của các mẫu ...
Aconcagua

Cách tiếp cận của Essels hoàn toàn khác: Các hàm ảo thông thường chấp nhận các tức thời mẫu khác nhau - và hàm mẫu cuối cùng trong lớp dẫn xuất chỉ phục vụ để tránh sao chép mã và thậm chí không có phần đối lập trong lớp cơ sở ...
Aconcagua

2

Trong khi một câu hỏi cũ hơn đã được nhiều người trả lời, tôi tin rằng một phương pháp cô đọng, không quá khác biệt so với các câu hỏi khác được đăng, là sử dụng một macro nhỏ để giúp dễ dàng sao chép khai báo lớp.

// abstract.h

// Simply define the types that each concrete class will use
#define IMPL_RENDER() \
    void render(int a, char *b) override { render_internal<char>(a, b); }   \
    void render(int a, short *b) override { render_internal<short>(a, b); } \
    // ...

class Renderable
{
public:
    // Then, once for each on the abstract
    virtual void render(int a, char *a) = 0;
    virtual void render(int a, short *b) = 0;
    // ...
};

Vì vậy, bây giờ, để thực hiện lớp con của chúng tôi:

class Box : public Renderable
{
public:
    IMPL_RENDER() // Builds the functions we want

private:
    template<typename T>
    void render_internal(int a, T *b); // One spot for our logic
};

Lợi ích ở đây là, khi thêm một loại mới được hỗ trợ, tất cả có thể được thực hiện từ tiêu đề trừu tượng và có thể sửa nó trong nhiều tệp nguồn / tiêu đề.


0

Ít nhất với gcc 5.4, các hàm ảo có thể là thành viên mẫu nhưng phải là mẫu.

#include <iostream>
#include <string>
class first {
protected:
    virtual std::string  a1() { return "a1"; }
    virtual std::string  mixt() { return a1(); }
};

class last {
protected:
    virtual std::string a2() { return "a2"; }
};

template<class T>  class mix: first , T {
    public:
    virtual std::string mixt() override;
};

template<class T> std::string mix<T>::mixt() {
   return a1()+" before "+T::a2();
}

class mix2: public mix<last>  {
    virtual std::string a1() override { return "mix"; }
};

int main() {
    std::cout << mix2().mixt();
    return 0;
}

Đầu ra

mix before a2
Process finished with exit code 0

0

Thử cái này:

Viết trong classeder.h:

template <typename T>
class Example{
public:
    T c_value;

    Example(){}

    T Set(T variable)
    {
          return variable;
    }

    virtual Example VirtualFunc(Example paraM)
    {
         return paraM.Set(c_value);
    }

Kiểm tra, nếu làm việc với cái này, để viết mã này trong main.cpp:

#include <iostream>
#include <classeder.h>

int main()
{
     Example exmpl;
     exmpl.c_value = "Hello, world!";
     std::cout << exmpl.VirtualFunc(exmpl);
     return 0;
}
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.