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ì?
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ì?
Câu trả lời:
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 .
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.
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.
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
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 .)
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ỏ.
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.
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 .
Đ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à:
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.
Foo
trỏ 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>
.
Không, các chức năng thành viên mẫu không thể là ảo.
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.
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
Để 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.
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 ( int
và double
).
Ở đó, 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 TMethod
trong 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::Method
thực sự là dư thừa cho mã thực ( VMethod
có 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ế.
Base
lớ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 ...
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 đề.
Í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
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;
}