2011-10-04 78 views
14

Có thể tìm thấy kích thước của đối tượng lớp dẫn xuất bằng cách sử dụng con trỏ lớp cơ sở, khi bạn không biết loại có nguồn gốc.tìm kích thước của đối tượng lớp dẫn xuất bằng cách sử dụng con trỏ lớp cơ sở

Cảm ơn bạn.

+0

Nếu bạn không biết loại, sử dụng RTTI. – SpeedBirdNine

+0

@SpeedBirdNine: Không chắc chắn làm thế nào tôi có thể sử dụng RTTI trong trường hợp này. Tôi có cần phải biết tất cả các loại có nguồn gốc – blueskin

+1

Phương pháp đơn giản nhất là thêm phương thức getSize() vào lớp cơ sở và mở rộng nó trong nguồn gốc. – Lalaland

Trả lời

23

Không có cách nào trực tiếp, nhưng bạn có thể viết một lớp con phương thức ảo size() có thể triển khai. Một lớp mẫu trung gian có thể tự động hóa công việc chân.

struct base { 
    virtual size_t size() const =0; 
    virtual ~base() { } 
}; 

template<typename T> 
struct intermediate : base { 
    virtual size_t size() const { return sizeof(T); } 
}; 

struct derived : intermediate<derived> 
{ }; 

này đòi hỏi hệ thống cấp bậc của bạn được đa hình ... Tuy nhiên, yêu cầu hành vi dựa trên các loại động của một đối tượng chứ không phải là loại tĩnh của nó là một phần của định nghĩa về hành vi đa hình. Vì vậy, điều này sẽ không thêm một bảng v vào trường hợp sử dụng trung bình, vì ít nhất bạn có thể đã có một destructor ảo.

Việc triển khai cụ thể này giới hạn cây thừa kế của bạn thành một cấp duy nhất mà không nhận được nhiều thừa kế [tức là loại có nguồn gốc từ derived sẽ không ghi đè riêng của mình là size]. Có một biến thể hơi phức tạp hơn xung quanh đó.

struct base { /*as before */ }; 

template<typename Derived, typename Base> 
struct intermediate : Base { 
    virtual size_t size() const { return sizeof(Derived); } 
}; 

struct derived : intermediate<derived, base> 
{ }; 

struct further_derived : intermediate<further_derived, derived> 
{ }; 

Về cơ bản, điều này chèn một intermediate ở giữa mỗi lớp thực tế của hệ thống phân cấp của bạn, mỗi trọng size với các hành vi thích hợp, và bắt nguồn từ loại cơ sở thực tế. Lặp lại quảng cáo nauseum.

//what you want 
base >> derived 
    >> more_deriveder 
    >> most_derivedest 

//what you get 
base >> intermediate<derived, base> 
    >> derived >> intermediate<more_deriveder, derived> 
    >> more_deriveder >> intermediate<most_derivedest, more_deriveder> 
    >> most_derivedest 

Một số thư viện kiểu mixin sử dụng lược đồ như vậy, để mixin có thể được thêm vào hệ thống phân cấp hiện có mà không cần đưa nhiều thừa kế. Cá nhân, tôi hiếm khi sử dụng nhiều hơn một mức độ thừa kế duy nhất, vì vậy tôi không bận tâm với sự phức tạp thêm, nhưng mileage của bạn có thể khác nhau.

+1

Sử dụng đẹp [CRTP] (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). –

+0

Lúc đầu không nhận thấy, 'trung gian' cần lấy từ' cơ số'. –

+0

Ý định là rõ ràng, nhưng 'trung gian' nên kế thừa từ' cơ sở' cho đầy đủ :) – sbk

3

Tôi không nghĩ rằng nó có thể được thực hiện, bởi vì sizeof hoạt động trên các loại thời gian biên dịch. Bạn có thể định nghĩa một hàm ảo Size trong lớp cơ sở và ghi đè nó cho mỗi lớp dẫn xuất.

+1

Lớp con sẽ báo cáo kích thước không đúng nếu bạn quên ghi đè phương thức kích thước. – Frigo

0

Do thiếu sự phản ánh trong C++, điều này thường không thể xảy ra với các lớp tùy ý theo ý thích. Có một số cách giải quyết tuy nhiên. Bạn có thể viết một phương thức virtual size() như những người khác đã gợi ý. Bạn cũng có thể sử dụng mẫu mẫu tò mò định kỳ, còn được thừa kế từ Register<T> nhưng tôi sẽ không đề xuất nó, chi phí vtable 4 byte cho mỗi đối tượng, lớp con của T báo cáo kích thước không chính xác và điều chỉnh kết quả trong đa kế thừa.

Cách tốt nhất là nên sử dụng một lớp học để đăng ký, lưu trữ và truy vấn thông tin kích thước động, mà không sửa đổi các lớp học mà bạn muốn truy vấn:

EDIT: Khi nó quay ra, do ngữ nghĩa không phù hợp của typeid, nó vẫn cần các lớp học với vtables, xem các ý kiến.

#include <cstddef> 
#include <exception> 
#include <iostream> 
#include <map> 
#include <typeinfo> 

using namespace std; 

class ClassNotFoundException 
: public exception 
{}; 

class Register 
{ 

    public: 

     template <class T> 
     static void reg (T* = NULL) 
     { 
      // could add other qualifiers 
      v[&typeid(T)] = sizeof(T); 
      v[&typeid(const T)] = sizeof(T); 
      v[&typeid(T*)] = sizeof(T); 
      v[&typeid(const T*)] = sizeof(T); 
     } 

     template <class T> 
     static int getSize (const T& x) 
     { 
      const type_info* id = &typeid(x); 
      if(v.find(id) == v.end()){ 
       throw ClassNotFoundException(); 
      } 
      return v[id]; 
     } 

     template <class T> 
     static int getSize (T* x) 
     { 
      return getSize(*x); 
     } 

     template <class T> 
     static int getSize (const T* x) 
     { 
      return getSize(*x); 
     } 

    protected: 

     static map<const type_info*, int> v; 

}; 

map<const type_info*, int> Register::v; 

class A 
{ 
    public: 
     A() : x() {} 
     virtual ~A() {} 
    protected: 
     int x; 
}; 

class B 
: public A 
{ 
    public: 
     B() : y() {} 
     virtual ~B() {} 
    protected: 
     int y; 
}; 

int main() 
{ 
    Register::reg<A>(); 
    Register::reg<B>(); 

    A* a = new B(); 
    const A* b = new B(); 

    cout << Register::getSize(a) << endl; 
    cout << Register::getSize(b) << endl; 
} 
+0

Sử dụng typeid yêu cầu lớp phải có vtable để làm việc (như bạn đã được hiển thị trong mã của bạn với dtor ảo). Vì vậy, bạn vẫn có phí vtable cho các đối tượng của bạn, chi phí bổ sung cho bản đồ đăng ký và yêu cầu đăng ký lớp học, vì vậy bạn không thể có được một kích thước nếu bạn cố gắng làm điều đó trước khi đăng ký. Xin lỗi, nhưng nó không thực sự trông giống như cách tốt nhất để đi. –

+0

Chết tiệt, tôi bỏ qua rằng typeid không có ngữ nghĩa nhất quán. Nó thực sự báo cáo sai loại và kích cỡ sai cho các lớp con của một lớp mà không có vtable. Bạn phải đảm bảo rằng tất cả các lớp của bạn đều có các trình phá hủy ảo, rất may là có những cảnh báo cho điều đó. Và nó vẫn có lợi thế so với hai phương thức khác, nếu bạn quên đăng ký một lớp, nó sẽ ném một ngoại lệ thay vì báo cáo kích thước sai hoặc đòi hỏi nhiều phương thức thừa kế và mơ hồ. – Frigo

+0

nó khá tầm thường để kiểm tra kích thước() đã được overriden và ném ngoại lệ nếu không, do đó, nó không phải là lợi thế vốn có của giải pháp của bạn và tôi không thực sự thấy lý do tại sao bạn sẽ cần nhiều thừa kế cho một giải pháp với CRTP. –

0

Xét câu trả lời tốt đẹp của @Dennis Zickefoose, có một trường hợp mà bạn có thể thực hiện nhiều cấp độ thừa kế mà đòi hỏi bạn không có chức năng ảo hay một lớp trung gian giữa mỗi lớp thừa kế và sự phức tạp thêm.

Và đó là khi tất cả các lớp trung gian (không phải lá) trong phân cấp thừa kế là trừu tượng các lớp, tức là chúng không được khởi tạo.

Nếu trường hợp đó xảy ra, bạn có thể viết các lớp trừu tượng không lá (templated) lại trên các loại bê tông có nguồn gốc.

Ví dụ dưới đây cho thấy điều này: sử dụng

template <class TDerived> 
class Shape  // Base 
{ 
public: 
    float centerX; 
    float centerY; 

    int getSize() 
    { return sizeof(TDerived); } 

    void demo() 
    { 
     std::cout 
      << static_cast<TDerived*>(this)->getSize() 
      << std::endl; 
    } 
}; 

class Circle : public Shape<Circle> 
{ 
public: 
    float radius; 
}; 

class Square : public Shape<Square> 
{ 
    // other data... 
}; 

template <class TDerived> 
class Shape3D : public Shape<TDerived> 
    // Note that this class provides the underlying class the template argument 
    // it receives itself, and note that Shape3D is (at least conceptually) 
    // abstract because we can't directly instantiate it without providing it 
    // the concrete type we want, and because we shouldn't. 
{ 
public: 
    float centerZ; 
}; 

class Cube : public Shape3D<Cube> 
{ 
    // other data... 
}; 

class Polyhedron : public Shape3D<Polyhedron> 
{ 
public: 
    typedef float Point3D[3]; 

    int numPoints; 
    Point3D points[MAX_POINTS]; 

    int getSize() // override the polymorphic function 
    { return sizeof(numPoints) + numPoints * sizeof(Point3D); } 
    // This is for demonstration only. In real cases, care must be taken about memory alignment issues to correctly determine the size of Polyhedron. 
}; 


mẫu:

Circle c; 
c.demo(); 

Polyhedron p; 
p.numPoints = 4; 
p.demo(); 
Các vấn đề liên quan