C ++ thành viên ảo tĩnh?


140

Có thể trong C ++ có chức năng thành viên là cả hai staticvirtualkhông? Rõ ràng, không có cách nào đơn giản để làm điều đó ( static virtual member();là một lỗi biên dịch), nhưng có ít nhất một cách để đạt được hiệu quả tương tự không?

I E:

struct Object
{
     struct TypeInformation;

     static virtual const TypeInformation &GetTypeInformation() const;
};

struct SomeObject : public Object
{
     static virtual const TypeInformation &GetTypeInformation() const;
};

Thật hợp lý khi sử dụng GetTypeInformation()cả trên một thể hiện ( object->GetTypeInformation()) và trên một lớp ( SomeObject::GetTypeInformation()), có thể hữu ích cho việc so sánh và quan trọng đối với các mẫu.

Các cách duy nhất tôi có thể nghĩ đến liên quan đến việc viết hai hàm / hàm và hằng số, mỗi lớp hoặc sử dụng macro.

Bất kỳ giải pháp nào khác?


12
Chỉ cần một nhận xét phụ: các phương thức tĩnh không thực thi trên bất kỳ trường hợp nào, điều đó có nghĩa là chúng không có con trỏ ẩn này. Điều đó đang được nói, consttrong một chữ ký phương thức đánh dấu thiscon trỏ ẩn là hằng số và không thể được áp dụng cho các phương thức tĩnh vì chúng thiếu tham số ẩn.
David Rodríguez - Dribeas

2
@cvb: Tôi nghiêm túc xem xét lại việc thay thế ví dụ của bạn bằng mã không liên quan đến sự phản chiếu. Theo cách hiện tại, bạn đang sắp xếp hai vấn đề riêng biệt (mặc dù có liên quan). Vâng, và tôi biết đó là 5 năm rưỡi kể từ khi bạn hỏi nó.
einpoklum

Một trong những tính năng được yêu cầu ngầm ở đây là để trình biên dịch kiểm tra xem mỗi đối tượng trong một hệ thống phân cấp thực hiện một giao diện cụ thể (trong đó một hoặc nhiều phương thức là tĩnh). Về cơ bản, một kiểm tra ảo thuần cho phương thức tĩnh có rất nhiều ý nghĩa, vì nếu bạn quên thêm phương thức tĩnh, thì trình biên dịch sẽ báo lỗi. ảo không phải là từ khóa ở đây, nó trừu tượng hơn , đó là loại từ đồng nghĩa trong C ++, ngoại trừ trường hợp cụ thể này. Thật không may, hiện tại bạn không thể làm điều đó với C ++.
xryl669

Câu trả lời:


75

Không, không có cách nào để làm điều đó, vì điều gì sẽ xảy ra khi bạn gọi Object::GetTypeInformation()? Nó không thể biết phiên bản lớp dẫn xuất nào để gọi vì không có đối tượng liên quan đến nó.

Bạn sẽ phải biến nó thành một hàm ảo không tĩnh để hoạt động chính xác; nếu bạn cũng muốn có thể gọi phiên bản của một lớp dẫn xuất cụ thể mà hầu như không có phiên bản đối tượng, bạn cũng sẽ phải cung cấp phiên bản không ảo ảo thứ hai dự phòng.


8
Nếu bạn nghĩ về lớp tĩnh (hoặc các thành viên tĩnh) là một singleton, mọi thứ sẽ trở nên rõ ràng - trong trường hợp của bạn chỉ cần gọi Object :: GetTypeIn information - giống như cách gọi phương thức ảo thông thường trên thể hiện của lớp cơ sở . (Tất nhiên, nếu C ++ hỗ trợ các phương thức tĩnh ảo)
Spook 18/12/13

13
Đó là một lập luận hoàn toàn đặc biệt. Nếu bạn sử dụng lớp thay vì một đối tượng, nó sẽ tự nhiên sử dụng phiên bản từ lớp đó, thay vì thực hiện gửi ảo. Không có gì mới ở đó.
Ded

54

Nhiều người nói điều đó là không thể, tôi sẽ tiến thêm một bước và nói rằng điều đó không có ý nghĩa.

Một thành viên tĩnh là một cái gì đó không liên quan đến bất kỳ trường hợp nào, chỉ liên quan đến lớp.

Một thành viên ảo là một cái gì đó không liên quan trực tiếp đến bất kỳ lớp nào, chỉ liên quan đến một thể hiện.

Vì vậy, một thành viên ảo tĩnh sẽ là một cái gì đó không liên quan đến bất kỳ trường hợp hoặc bất kỳ lớp nào.


42
Nó hoàn toàn có ý nghĩa trong các ngôn ngữ nơi các lớp là giá trị hạng nhất - ví dụ Delphi có điều đó và cũng có các phương thức "ảo ảo".
Pavel Minaev

4
Chính xác. "Hàm ảo" là (theo định nghĩa) một hàm được liên kết động , tức là nó được chọn trong thời gian chạy tùy thuộc vào loại động của một đối tượng nhất định. Do đó, không có đối tượng = không có cuộc gọi ảo.
Kos

7
Tôi cũng nghĩ rằng ảo ảo là có ý nghĩa. Có thể định nghĩa các lớp giao diện và bao gồm các phương thức tĩnh phải được thực hiện trong lớp dẫn xuất.
bkausbk

34
Nó không có ý nghĩa đối với một static virtualphương thức, nhưng một phương thức static thuần túy virtual rất có ý nghĩa trong một giao diện.
Bret Kuhns

4
Nó là hoàn toàn có ý nghĩa để có một static const string MyClassSillyAdditionalName.
einpoklum

23

Tôi đã gặp vấn đề này vào một ngày khác: tôi đã có một số lớp chứa đầy các phương thức tĩnh nhưng tôi muốn sử dụng kế thừa và phương thức ảo và giảm sự lặp lại mã. Giải pháp của tôi là:

Thay vì sử dụng các phương thức tĩnh, hãy sử dụng một singleton với các phương thức ảo.

Nói cách khác, mỗi lớp nên chứa một phương thức tĩnh mà bạn gọi để lấy một con trỏ tới một thể hiện chung, duy nhất của lớp. Bạn có thể đặt các hàm tạo thực sự ở chế độ riêng tư hoặc được bảo vệ để mã bên ngoài không thể sử dụng sai bằng cách tạo các thể hiện bổ sung.

Trong thực tế, sử dụng một singleton rất giống như sử dụng các phương thức tĩnh ngoại trừ việc bạn có thể tận dụng sự kế thừa và phương thức ảo.


Điều đó sẽ làm tôi mất hiệu suất - trừ khi trình biên dịch có thể chắc chắn rằng: 1. Nó thực sự là một singleton và 2. Không có gì thừa hưởng từ nó, tôi không nghĩ rằng nó có thể tối ưu hóa tất cả các chi phí.
einpoklum

Nếu hiệu suất của loại điều này làm bạn lo lắng thì C # có lẽ là ngôn ngữ sai cho bạn.
Nate CK

3
Ah, điểm tốt. Rõ ràng đã được một thời gian kể từ khi tôi nghĩ về điều này kể từ khi tôi viết nó vào năm 2009. Hãy để tôi nói theo một cách khác, sau đó: nếu loại hiệu suất này làm bạn lo lắng thì có lẽ bạn nên tránh sử dụng hoàn toàn quyền thừa kế. Người đăng đặc biệt yêu cầu các phương thức ảo, vì vậy thật lạ khi bạn đến đây để phàn nàn về chi phí của các phương thức ảo.
Nate CK

15

Điều đó là có thể!

Nhưng chính xác những gì có thể, hãy thu hẹp lại. Mọi người thường muốn một số loại "hàm ảo tĩnh" vì sao chép mã cần thiết để có thể gọi cùng một chức năng thông qua cuộc gọi tĩnh "someDerivingClass :: myfunction ()" và cuộc gọi đa hình "base_ class_pulum-> myfeft ()". Phương pháp "Pháp lý" để cho phép chức năng đó là sao chép các định nghĩa hàm:

class Object
{
public:
    static string getTypeInformationStatic() { return "base class";}
    virtual string getTypeInformation() { return getTypeInformationStatic(); }
}; 
class Foo: public Object
{
public:
    static string getTypeInformationStatic() { return "derived class";}
    virtual string getTypeInformation() { return getTypeInformationStatic(); }
};

Điều gì xảy ra nếu lớp cơ sở có một số lượng lớn các hàm tĩnh và lớp dẫn xuất phải ghi đè lên tất cả chúng và người ta quên cung cấp một định nghĩa trùng lặp cho hàm ảo. Phải, chúng tôi sẽ gặp một số lỗi lạ trong thời gian chạy rất khó theo dõi. Nguyên nhân trùng lặp mã là một điều xấu. Sau đây cố gắng giải quyết vấn đề này (và tôi muốn nói trước rằng nó hoàn toàn an toàn và không chứa bất kỳ phép thuật đen nào như typeid's hoặc Dynamic_cast's :)

Vì vậy, chúng tôi muốn chỉ cung cấp một định nghĩa về getTypeInform () cho mỗi lớp dẫn xuất và rõ ràng nó phải là một định nghĩa về tĩnhhàm vì không thể gọi "someDerivingClass :: getTypeIn information ()" nếu getTypeInform () là ảo. Làm thế nào chúng ta có thể gọi hàm tĩnh của lớp dẫn xuất thông qua con trỏ đến lớp cơ sở? Không thể với vtable vì vtable chỉ lưu trữ các con trỏ tới các hàm ảo và vì chúng tôi đã quyết định không sử dụng các hàm ảo, chúng tôi không thể sửa đổi vtable vì lợi ích của chúng tôi. Sau đó, để có thể truy cập hàm tĩnh cho lớp dẫn xuất thông qua con trỏ đến lớp cơ sở, chúng ta phải lưu trữ bằng cách nào đó loại đối tượng trong lớp cơ sở của nó. Một cách tiếp cận là tạo templatized lớp cơ sở bằng cách sử dụng "mẫu khuôn mẫu lặp lại một cách tò mò" nhưng nó không phù hợp ở đây và chúng tôi sẽ sử dụng một kỹ thuật gọi là "xóa kiểu":

class TypeKeeper
{
public:
    virtual string getTypeInformation() = 0;
};
template<class T>
class TypeKeeperImpl: public TypeKeeper
{
public:
    virtual string getTypeInformation() { return T::getTypeInformationStatic(); }
};

Bây giờ chúng ta có thể lưu trữ loại đối tượng trong lớp "Đối tượng" cơ sở với biến "người giữ":

class Object
{
public:
    Object(){}
    boost::scoped_ptr<TypeKeeper> keeper;

    //not virtual
    string getTypeInformation() const 
    { return keeper? keeper->getTypeInformation(): string("base class"); }

};

Trong một lớp dẫn xuất, người quản lý phải được khởi tạo trong quá trình xây dựng:

class Foo: public Object
{
public:
    Foo() { keeper.reset(new TypeKeeperImpl<Foo>()); }
    //note the name of the function
    static string getTypeInformationStatic() 
    { return "class for proving static virtual functions concept"; }
};

Hãy thêm đường cú pháp:

template<class T>
void override_static_functions(T* t)
{ t->keeper.reset(new TypeKeeperImpl<T>()); }
#define OVERRIDE_STATIC_FUNCTIONS override_static_functions(this)

Bây giờ tuyên bố của con cháu trông giống như:

class Foo: public Object
{
public:
    Foo() { OVERRIDE_STATIC_FUNCTIONS; }
    static string getTypeInformationStatic() 
    { return "class for proving static virtual functions concept"; }
};

class Bar: public Foo
{
public:
    Bar() { OVERRIDE_STATIC_FUNCTIONS; }
    static string getTypeInformationStatic() 
    { return "another class for the same reason"; }
};

sử dụng:

Object* obj = new Foo();
cout << obj->getTypeInformation() << endl;  //calls Foo::getTypeInformationStatic()
obj = new Bar();
cout << obj->getTypeInformation() << endl;  //calls Bar::getTypeInformationStatic()
Foo* foo = new Bar();
cout << foo->getTypeInformation() << endl; //calls Bar::getTypeInformationStatic()
Foo::getTypeInformation(); //compile-time error
Foo::getTypeInformationStatic(); //calls Foo::getTypeInformationStatic()
Bar::getTypeInformationStatic(); //calls Bar::getTypeInformationStatic()

Ưu điểm:

  1. ít sao chép mã hơn (nhưng chúng ta phải gọi OVERRIDE_STATIC_FUNCTIONS trong mỗi hàm tạo)

Nhược điểm:

  1. OVERRIDE_STATIC_FUNCTION trong mọi nhà xây dựng
  2. bộ nhớ và hiệu năng trên không
  3. tăng độ phức tạp

Vấn đề mở:

1) có các tên khác nhau cho các hàm tĩnh và ảo làm thế nào để giải quyết sự mơ hồ ở đây?

class Foo
{
public:
    static void f(bool f=true) { cout << "static";}
    virtual void f() { cout << "virtual";}
};
//somewhere
Foo::f(); //calls static f(), no ambiguity
ptr_to_foo->f(); //ambiguity

2) làm thế nào để ngầm gọi TOTRIDE_STATIC_FUNCTION bên trong mỗi nhà xây dựng?


+1 cho nỗ lực, mặc dù tôi không chắc điều này thanh lịch hơn là chỉ ủy thác chức năng cho một người độc thân bằng các phương thức ảo.
einpoklum

1
@einpoklum, tôi có thể nghĩ đến một tình huống khi điều này có thể thích hợp hơn. Giả sử chúng ta có rất nhiều mã máy khách đã gọi các phương thức tĩnh. Chuyển đổi từ phương thức tĩnh sang đơn lẻ bằng phương thức ảo sẽ yêu cầu thay đổi mã máy khách trong khi giải pháp được trình bày ở trên là không xâm lấn.
Alsk

Không cần từ khóa "ảo" cho "Foo :: getTypeInform" và "TypeKeeperImpl :: getTypeInform".
bartolo-otrit

12

Mặc dù Alsk đã đưa ra một câu trả lời khá chi tiết, tôi muốn thêm một giải pháp thay thế, vì tôi nghĩ rằng việc triển khai nâng cao của anh ta đã quá phức tạp.

Chúng tôi bắt đầu với một lớp cơ sở trừu tượng, cung cấp giao diện cho tất cả các loại đối tượng:

class Object
{
public:
    virtual char* GetClassName() = 0;
};

Bây giờ chúng tôi cần một triển khai thực tế. Nhưng để tránh phải viết cả phương thức tĩnh và phương thức ảo, chúng ta sẽ có các lớp đối tượng thực tế kế thừa các phương thức ảo. Điều này rõ ràng chỉ hoạt động, nếu lớp cơ sở biết cách truy cập hàm thành viên tĩnh. Vì vậy, chúng ta cần sử dụng một khuôn mẫu và truyền tên lớp đối tượng thực tế cho nó:

template<class ObjectType>
class ObjectImpl : public Object
{
public:
    virtual char* GetClassName()
    {
        return ObjectType::GetClassNameStatic();
    }
};

Cuối cùng, chúng ta cần thực hiện (các) đối tượng thực sự của mình. Ở đây chúng ta chỉ cần thực hiện hàm thành viên tĩnh, các hàm thành viên ảo sẽ được kế thừa từ lớp mẫu ObjectImpl, được khởi tạo với tên của lớp dẫn xuất, vì vậy nó sẽ truy cập vào các thành viên tĩnh của nó.

class MyObject : public ObjectImpl<MyObject>
{
public:
    static char* GetClassNameStatic()
    {
        return "MyObject";
    }
};

class YourObject : public ObjectImpl<YourObject>
{
public:
    static char* GetClassNameStatic()
    {
        return "YourObject";
    }
};

Hãy thêm một số mã để kiểm tra:

char* GetObjectClassName(Object* object)
{
    return object->GetClassName();
}

int main()
{
    MyObject myObject;
    YourObject yourObject;

    printf("%s\n", MyObject::GetClassNameStatic());
    printf("%s\n", myObject.GetClassName());
    printf("%s\n", GetObjectClassName(&myObject));
    printf("%s\n", YourObject::GetClassNameStatic());
    printf("%s\n", yourObject.GetClassName());
    printf("%s\n", GetObjectClassName(&yourObject));

    return 0;
}

Phụ lục (ngày 12 tháng 1 năm 2019):

Thay vì sử dụng hàm GetClassNameStatic (), bạn cũng có thể định nghĩa tên lớp là thành viên tĩnh, thậm chí là "nội tuyến", mà IIRC hoạt động kể từ C ++ 11 (không sợ tất cả các công cụ sửa đổi :)):

class MyObject : public ObjectImpl<MyObject>
{
public:
    // Access this from the template class as `ObjectType::s_ClassName` 
    static inline const char* const s_ClassName = "MyObject";

    // ...
};

11

Điều đó là có thể. Thực hiện hai chức năng: tĩnh và ảo

struct Object{     
  struct TypeInformation;
  static  const TypeInformation &GetTypeInformationStatic() const 
  { 
      return GetTypeInformationMain1();
  }
  virtual const TypeInformation &GetTypeInformation() const
  { 
      return GetTypeInformationMain1();
  }
protected:
  static const TypeInformation &GetTypeInformationMain1(); // Main function
};

struct SomeObject : public Object {     
  static  const TypeInformation &GetTypeInformationStatic() const 
  { 
      return GetTypeInformationMain2();
  }
  virtual const TypeInformation &GetTypeInformation() const
  { 
      return GetTypeInformationMain2();
  }
protected:
  static const TypeInformation &GetTypeInformationMain2(); // Main function
};

4
Ngoài ra, các phương thức tĩnh không thể là const. Nó chỉ không có ý nghĩa, trường hợp nào họ sẽ không đột biến?
David Rodríguez - Dribeas

1
Điều này chủ yếu chỉ là sao chép mã. Ý tưởng là cho các lớp con chỉ cần có thành viên const tĩnh, không phải có mã truy cập nó.
einpoklum

8

Không, điều này là không thể, bởi vì các hàm thành viên tĩnh thiếu một thiscon trỏ. Và các thành viên tĩnh (cả hàm và biến) không thực sự là thành viên lớp. Chúng tình cờ được gọi ClassName::membervà tuân thủ các chỉ định truy cập lớp. Lưu trữ của họ được xác định ở đâu đó bên ngoài lớp học; lưu trữ không được tạo mỗi khi bạn khởi tạo một đối tượng của lớp. Con trỏ đến các thành viên trong lớp là đặc biệt về ngữ nghĩa và cú pháp. Một con trỏ tới một thành viên tĩnh là một con trỏ bình thường trong tất cả các vấn đề.

các hàm ảo trong một lớp cần thiscon trỏ và rất được ghép với lớp, do đó chúng không thể tĩnh.


1
Chỉ các hàm không tĩnh cần một this con trỏ. các hàm tĩnh không đặc trưng cho một thể hiện và sẽ không cần đến nó. Vì vậy - đó không phải là lý do thành viên tĩnh ảo là không thể.
einpoklum

7

Vâng, một câu trả lời khá muộn nhưng có thể sử dụng mẫu mẫu định kỳ tò mò. Bài viết trên wikipedia này có thông tin bạn cần và cũng là ví dụ dưới đa hình tĩnh là những gì bạn được yêu cầu.


3

Tôi nghĩ những gì bạn đang cố gắng có thể được thực hiện thông qua các mẫu. Tôi đang cố đọc giữa dòng ở đây. Những gì bạn đang cố gắng làm là gọi một phương thức từ một số mã, trong đó nó gọi một phiên bản dẫn xuất nhưng người gọi không chỉ định lớp nào. Thí dụ:

class Foo {
public:
    void M() {...}
};

class Bar : public Foo {
public:
    void M() {...}
};

void Try()
{
    xxx::M();
}

int main()
{
    Try();
}

Bạn muốn Try () gọi phiên bản Bar của M mà không chỉ định Bar. Cách bạn làm điều đó cho thống kê là sử dụng một mẫu. Vì vậy, thay đổi nó như vậy:

class Foo {
public:
    void M() {...}
};

class Bar : public Foo {
public:
    void M() {...}
};

template <class T>
void Try()
{
    T::M();
}

int main()
{
    Try<Bar>();
}

1
Nếu bạn thụt mã của bạn 4 dấu cách, bạn có thể tự động định dạng mã. Ngoài ra, tôi tin rằng bạn có thể sử dụng đánh dấu lại để đạt được mục đích tương tự.
chollida

1
Đây là điều hiển nhiên tôi đã bỏ lỡ. Cảm ơn bạn. Tuy nhiên, các thành viên pubic là lạ.
allesblinkt

M () không phải là hàm tĩnh. Làm thế nào nó được gọi là T :: M ()?
DDukDDak99

3

Không, Hàm thành viên tĩnh không thể là ảo .since khái niệm ảo được giải quyết trong thời gian chạy với sự trợ giúp của vptr và vptr không phải là thành viên tĩnh của class.due với chức năng thành viên tĩnh đó không thể truy cập vptr để thành viên tĩnh có thể Sẽ là ảo.


2
Chỉ các phương thức ảo dành riêng cho cá thể yêu cầu vtable của cá thể. Bạn có thể có một tĩnh - một lớp mỗi - vtable. Và nếu bạn muốn biết về các thể hiện, chỉ cần trỏ từ vtable của cá thể đó đến các thống kê lớp vtable.
einpoklum

2

Điều đó là không thể, nhưng đó chỉ là một thiếu sót. Đó không phải là điều "không có ý nghĩa" như nhiều người dường như tuyên bố. Để rõ ràng, tôi đang nói về một cái gì đó như thế này:

struct Base {
  static virtual void sayMyName() {
    cout << "Base\n";
  }
};

struct Derived : public Base {
  static void sayMyName() override {
    cout << "Derived\n";
  }
};

void foo(Base *b) {
  b->sayMyName();
  Derived::sayMyName(); // Also would work.
}

Đây là 100% một cái gì đó có thể được thực hiện (nó chỉ không có) và tôi tranh luận một cái gì đó hữu ích.

Xem xét cách các chức năng ảo bình thường hoạt động. Xóa statics và thêm vào một số thứ khác và chúng tôi có:

struct Base {
  virtual void sayMyName() {
    cout << "Base\n";
  }
  virtual void foo() {
  }
  int somedata;
};

struct Derived : public Base {
  void sayMyName() override {
    cout << "Derived\n";
  }
};

void foo(Base *b) {
  b->sayMyName();
}

Điều này hoạt động tốt và về cơ bản những gì xảy ra là trình biên dịch tạo hai bảng, được gọi là VTables và gán các chỉ mục cho các hàm ảo như thế này

enum Base_Virtual_Functions {
  sayMyName = 0;
  foo = 1;
};

using VTable = void*[];

const VTable Base_VTable = {
  &Base::sayMyName,
  &Base::foo
};

const VTable Derived_VTable = {
  &Derived::sayMyName,
  &Base::foo
};

Tiếp theo mỗi lớp với các hàm ảo được tăng cường với một trường khác trỏ đến VTable của nó, vì vậy trình biên dịch về cơ bản thay đổi chúng thành như sau:

struct Base {
  VTable* vtable;
  virtual void sayMyName() {
    cout << "Base\n";
  }
  virtual void foo() {
  }
  int somedata;
};

struct Derived : public Base {
  VTable* vtable;
  void sayMyName() override {
    cout << "Derived\n";
  }
};

Sau đó, những gì thực sự xảy ra khi bạn gọi b->sayMyName()? Về cơ bản này:

b->vtable[Base_Virtual_Functions::sayMyName](b);

(Tham số đầu tiên trở thành this.)

Được rồi, vậy nó sẽ hoạt động như thế nào với các hàm ảo tĩnh? Vâng, sự khác biệt giữa các chức năng thành viên tĩnh và không tĩnh là gì? Sự khác biệt duy nhất là cái sau có được một thiscon trỏ.

Chúng ta có thể làm chính xác như vậy với các hàm ảo tĩnh - chỉ cần loại bỏ thiscon trỏ.

b->vtable[Base_Virtual_Functions::sayMyName]();

Điều này sau đó có thể hỗ trợ cả hai cú pháp:

b->sayMyName(); // Prints "Base" or "Derived"...
Base::sayMyName(); // Always prints "Base".

Vì vậy, bỏ qua tất cả những người không tán thành. Nó ý nghĩa. Tại sao nó không được hỗ trợ? Tôi nghĩ rằng bởi vì nó có rất ít lợi ích và thậm chí có thể hơi khó hiểu.

Lợi thế kỹ thuật duy nhất so với một chức năng ảo thông thường là bạn không cần phải chuyển thissang chức năng đó nhưng tôi không nghĩ rằng điều đó sẽ tạo ra bất kỳ sự khác biệt có thể đo lường nào đối với hiệu suất.

Điều đó có nghĩa là bạn không có chức năng tĩnh và không tĩnh riêng cho các trường hợp khi bạn có một cá thể và khi bạn không có một cá thể, nhưng cũng có thể nhầm lẫn rằng nó chỉ thực sự "ảo" khi bạn sử dụng cuộc gọi ví dụ.


0

Không, không thể, vì các thành viên tĩnh bị ràng buộc tại thời gian biên dịch, trong khi các thành viên ảo bị ràng buộc trong thời gian chạy.


0

Đầu tiên, các câu trả lời là chính xác rằng những gì OP yêu cầu là một mâu thuẫn trong các điều khoản: các phương thức ảo phụ thuộc vào loại thời gian chạy của một thể hiện; các hàm tĩnh đặc biệt không phụ thuộc vào một thể hiện - chỉ dựa trên một loại. Điều đó nói rằng, có ý nghĩa khi có các hàm tĩnh trả về một cái gì đó cụ thể cho một loại. Ví dụ, tôi có một nhóm các lớp MouseTool cho mẫu Trạng thái và tôi bắt đầu có mỗi lớp có một hàm tĩnh trả về bộ sửa đổi bàn phím đi kèm với nó; Tôi đã sử dụng các hàm tĩnh đó trong hàm xuất xưởng đã tạo ra cá thể MouseTool chính xác. Hàm đó đã kiểm tra trạng thái chuột dựa trên MouseToolA :: keyboardModifier (), MouseToolB :: keyboardModifier (), v.v. và sau đó khởi tạo một trạng thái thích hợp. Tất nhiên sau đó tôi muốn kiểm tra xem nhà nước có đúng không nên tôi muốn viết một cái gì đó như "

Vì vậy, nếu bạn thấy mình muốn điều này, bạn có thể muốn thử lại giải pháp của mình. Tuy nhiên, tôi hiểu mong muốn có các phương thức tĩnh và sau đó gọi chúng một cách linh hoạt dựa trên kiểu động của một thể hiện. Tôi nghĩ rằng Mô hình khách truy cập có thể cung cấp cho bạn những gì bạn muốn. Nó cung cấp cho bạn những gì bạn muốn. Đó là một chút mã bổ sung, nhưng nó có thể hữu ích cho các khách truy cập khác.

Xem: http://en.wikipedia.org/wiki/Visitor_potype để làm nền.

struct ObjectVisitor;

struct Object
{
     struct TypeInformation;

     static TypeInformation GetTypeInformation();
     virtual void accept(ObjectVisitor& v);
};

struct SomeObject : public Object
{
     static TypeInformation GetTypeInformation();
     virtual void accept(ObjectVisitor& v) const;
};

struct AnotherObject : public Object
{
     static TypeInformation GetTypeInformation();
     virtual void accept(ObjectVisitor& v) const;
};

Sau đó, cho từng đối tượng cụ thể:

void SomeObject::accept(ObjectVisitor& v) const {
    v.visit(*this); // The compiler statically picks the visit method based on *this being a const SomeObject&.
}
void AnotherObject::accept(ObjectVisitor& v) const {
    v.visit(*this); // Here *this is a const AnotherObject& at compile time.
}

và sau đó xác định khách truy cập cơ sở:

struct ObjectVisitor {
    virtual ~ObjectVisitor() {}
    virtual void visit(const SomeObject& o) {} // Or = 0, depending what you feel like.
    virtual void visit(const AnotherObject& o) {} // Or = 0, depending what you feel like.
    // More virtual void visit() methods for each Object class.
};

Sau đó, khách truy cập cụ thể chọn chức năng tĩnh thích hợp:

struct ObjectVisitorGetTypeInfo {
    Object::TypeInformation result;
    virtual void visit(const SomeObject& o) {
        result = SomeObject::GetTypeInformation();
    }
    virtual void visit(const AnotherObject& o) {
        result = AnotherObject::GetTypeInformation();
    }
    // Again, an implementation for each concrete Object.
};

cuối cùng, sử dụng nó:

void printInfo(Object& o) {
    ObjectVisitorGetTypeInfo getTypeInfo;
    Object::TypeInformation info = o.accept(getTypeInfo).result;
    std::cout << info << std::endl;
}

Ghi chú:

  • Constness trái như một bài tập.
  • Bạn đã trả lại một tài liệu tham khảo từ một tĩnh. Trừ khi bạn có một singleton, đó là câu hỏi.

Nếu bạn muốn tránh các lỗi sao chép-dán trong đó một trong các phương thức truy cập của bạn gọi hàm tĩnh sai, bạn có thể sử dụng hàm trợ giúp templated (không thể là ảo) với khách truy cập của bạn với một mẫu như thế này:

struct ObjectVisitorGetTypeInfo {
    Object::TypeInformation result;
    virtual void visit(const SomeObject& o) { doVisit(o); }
    virtual void visit(const AnotherObject& o) { doVisit(o); }
    // Again, an implementation for each concrete Object.

  private:
    template <typename T>
    void doVisit(const T& o) {
        result = T::GetTypeInformation();
    }
};

các phương thức tĩnh ảo, nếu chúng tồn tại, sẽ không phụ thuộc vào bất kỳ thứ gì trong một cá thể - nhưng cá thể sẽ cần phải biết loại của nó để gọi chúng. Điều này có thể được trình biên dịch xử lý (ví dụ: bằng cách sử dụng một số cấu trúc dữ liệu đơn lớp với các con trỏ tới các phương thức và thành viên tĩnh ảo.) Nó chắc chắn không phải là một mâu thuẫn trong các điều khoản.
einpoklum

Có hay không một mâu thuẫn trong các điều khoản là một câu hỏi về ngữ nghĩa. Người ta có thể tưởng tượng C ++ cho phép gọi statics từ một ví dụ (ví dụ, Foo foo; ... foo::bar();thay vì Foo::bar();). Điều đó không giống decltype(foo)::bar();nhưng điều đó một lần nữa sẽ bị ràng buộc tĩnh. Cách tiếp cận khách truy cập có vẻ là một cách hợp lý để có được hành vi này mà không làm cho phương thức tĩnh trở thành phương thức const ảo.
Ben

0

Với c ++, bạn có thể sử dụng kế thừa tĩnh với phương thức crt. Ví dụ, nó được sử dụng rộng rãi trên mẫu cửa sổ atl & wtl.

Xem https://en.wikipedia.org/wiki/Cantlyly_recurring_template_potype

Để đơn giản, bạn có một lớp được tạo ra từ chính nó giống như lớp my class: myancestor. Từ thời điểm này, lớp myancestor có thể gọi hàm T :: YourImpl tĩnh của bạn.


-1

Có lẽ bạn có thể thử giải pháp của tôi dưới đây:

class Base {
public:
    Base(void);
    virtual ~Base(void);

public:
    virtual void MyVirtualFun(void) = 0;
    static void  MyStaticFun(void) { assert( mSelf != NULL); mSelf->MyVirtualFun(); }
private:
    static Base* mSelf;
};

Base::mSelf = NULL;

Base::Base(void) {
    mSelf = this;
}

Base::~Base(void) {
    // please never delete mSelf or reset the Value of mSelf in any deconstructors
}

class DerivedClass : public Base {
public:
    DerivedClass(void) : Base() {}
    ~DerivedClass(void){}

public:
    virtual void MyVirtualFun(void) { cout<<"Hello, it is DerivedClass!"<<endl; }
};

int main() {
    DerivedClass testCls;
    testCls.MyStaticFun(); //correct way to invoke this kind of static fun
    DerivedClass::MyStaticFun(); //wrong way
    return 0;
}

Vâng, tôi biết, 4 năm. Giải thích -score cho những người không muốn đọc mã trong nhiều chi tiết. Base::mSelfđề cập đến thể hiện được xây dựng NHIỀU NHẤT của bất kỳ lớp dẫn xuất nào, ngay cả khi thể hiện đó đã bị hủy . Vì vậy, class D1 : public Base ...; class D2 : public Base ...; ...; D1* pd1 = new D1(); D2* pd2 = new D2(); pd1->MyStaticFun(); /* calls D2::MyVirtualFun() */ delete pd2; pd1->MyStaticFun(); /* calls via deleted pd2 */đó không phải là những gì muốn.
Jesse Chisholm

-3

Giống như những người khác đã nói, có 2 thông tin quan trọng:

  1. không có thiscon trỏ khi thực hiện cuộc gọi hàm tĩnh và
  2. các thisđiểm con trỏ đến cấu trúc nơi bảng ảo, hoặc thunk, được sử dụng để tìm kiếm phương pháp runtime để gọi.

Một hàm tĩnh được xác định tại thời điểm biên dịch.

Tôi đã cho thấy ví dụ mã này trong các thành viên tĩnh C ++ trong lớp ; nó cho thấy rằng bạn có thể gọi một phương thức tĩnh được cung cấp một con trỏ null:

struct Foo
{
    static int boo() { return 2; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    Foo* pFoo = NULL;
    int b = pFoo->boo(); // b will now have the value 2
    return 0;
}

6
Về mặt kỹ thuật, đây là hành vi không xác định. Bạn không thể trì hoãn một con trỏ null vì bất kỳ lý do nào. Điều duy nhất bạn có thể làm với một con trỏ null là a) gán một con trỏ khác cho nó và b) so sánh nó với một con trỏ khác.
KeithB

1
Hơn nữa, bạn chỉ có thể so sánh nó với sự bình đẳng (hoặc bất bình đẳng_ với một con trỏ khác, không đặt hàng. Tức là p < null, p >= nullv.v ... đều không được xác định là tốt.
Pavel Minaev

1
@KeithB - ​​Để hoàn thiện, bạn cũng có thể gọi xóa một cách an toàn trên một con trỏ null.
Steve Rowe
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.