2017-07-14 25 views
5

Tôi chỉ khám phá ra hành vi sau: có đối tượng thuộc loại B bắt nguồn từ loại A, loại cuối cùng trong khi xây dựng AA và không B. Điều này có thể được quan sát với các ví dụ sau:Loại đối tượng thay đổi trong khi xây dựng

#include <iostream> 
#include <typeinfo> 

class A 
{ 
    public: 
     A() { std::cout << &typeid(*this) << std::endl; } 
}; 

class B : public A 
{ 
    public: 
     B() : A() { std::cout << &typeid(*this) << std::endl; } 
}; 

int main() 
{ 
    A a; 
    B b; 
    return 0; 
} 

Một chạy của mã này (biên soạn với gcc 4.8.5) như sau:

0x400ae0 
0x400ae0 
0x400ac0 

Chúng ta có thể thấy rằng kiểu trả về bởi typeid trong A::A()A và không B, và sau đó loại cuối cùng thay đổi để trở thành B.

Tại sao?

Có thể biết loại cuối cùng "thực" trong khi xây dựng lớp cha không?

bối cảnh của tôi là như sau:

Tôi có một tầng lớp phụ huynh Resource và một số lớp kế thừa từ nó. Tôi cũng có một thông báo ResourceManager bởi mỗi lần tạo tài nguyên và phải biết loại cuối cùng của tài nguyên được tạo. Những gì tôi đang làm để tránh mã trùng lặp là điều sau đây, nhưng nó không hoạt động:

class Resource 
{ 
    public: 
    Resource() { ResourceManager::notifyCreation(*this); } 
    ~Resource() { ResourceManager::notifyDestruction(*this); } 
}; 
class MyResource : public Resource 
{ 
    // I don't have to care to the manager here 
}; 

Tôi biết tôi có thể làm được thông báo trong mỗi constructor/destructor của trẻ em, nhưng nó ít mạnh mẽ (có thể lỗi nếu một tài nguyên được instanciated mà không cần thông báo cho người quản lý). Bạn có ý tưởng giải pháp nào không?

+2

Umm ... '& typeid (....)' ?? – WhiZTiM

+3

'* this' trong' A' luôn là 'A', tại sao bạn mong đợi' typeid' khác? –

+0

@WhiZTiM tại sao? Giá trị trả về của typeid là duy nhất trong bộ nhớ cho mỗi loại. – Caduchon

Trả lời

5

Âm thanh như những gì bạn đang tìm kiếm là CRTP

template<typename Concrete> 
struct Resource 
{ 
    Resource() { ResourceManager::notifyCreation(*static_cast<Concrete*>(this)); } 
    ~Resource() { ResourceManager::notifyDestruction(*static_cast<Concrete*>(this)); } 
}; 

struct MyResource : Resource<MyResource> 
{ 

}; 

Lưu ý rằng MyResource là chưa hoàn thành xây dựng khi cuộc gọi đến notifyCreation được thực hiện. Địa chỉ của cá thể MyResource có thể được thực hiện, nhưng đó là tất cả những gì có thể được thực hiện cho cá thể. (Nhờ Caleth cho trỏ này ra)

Đặc biệt từ [class.cdtor]

Nếu toán hạng của typeid đề cập đến đối tượng được xây dựng hoặc phá hủy và loại tĩnh của các toán hạng không phải là các nhà xây dựng hoặc lớp destructor của cũng không phải một trong những căn cứ của nó, hành vi là không xác định.

Do đó ResourceManager sẽ phải được thực hiện một chút như thế này để cho phép sử dụng typeid

struct ResourceManager 
{ 
    template<typename T> 
    void notifyCreation(T&&) 
    { 
     add(typeid(T)); // can't apply to an expression 
    } 
    template<typename T> 
    void notifyDestruction(T&&) 
    { 
     remove(typeid(T)); // can't apply to an expression 
    } 
}; 
+0

Điều này chỉ an toàn nếu 'MyResource' không có thành viên nào ngoài' Tài nguyên ' – Caleth

+1

@Caleth Bạn có nghĩa là không còn lớp cơ sở nào nữa? Có, nhưng mã OP đã hiển thị trông như thế này và tôi đã không đi vào suy đoán những gì khác OP muốn –

+0

Không, tôi không có nghĩa là không có nhiều thành viên dữ liệu. Chúng chưa được khởi tạo tại điểm mà 'ResourceManager :: notifyCreation' được gọi, và đã bị hủy khi' ResourceManager :: notifyDestruction' được gọi là – Caleth

1

Không có cách nào tốt để làm điều đó trong một constructor như trong ví dụ của bạn, nhưng bạn có thể cung cấp một constructor đặc biệt cho A, tức là

A(const std::type_info &info) { 
    std::cout << info.name() << std::endl; 
} 

và trong B

B() : A(typeid(*this)) { 
    std::cout << typeid(*this).name() std::endl; 
} 

nếu bạn làm điều đó bên ngoài các nhà xây dựng, bạn cũng có thể cung cấp một hàm ảo trong 'A' và ghi đè lên nó trong 'B'.

Các vấn đề liên quan