2008-11-18 48 views
6

Tôi đã gặp phải một chút vấn đề phức tạp trong một số mã C++, được mô tả dễ dàng nhất bằng mã. Tôi có các lớp học giống như:Ghi đè biến thành viên trong C++

class MyVarBase 
{ 
} 

class MyVar : public MyVarBase 
{ 
    int Foo(); 
} 

class MyBase 
{ 
public: 
    MyBase(MyVarBase* v) : m_var(v) {} 
    virtual MyVarBase* GetVar() { return m_var; } 
private: 
    MyVarBase* m_var; 
} 

Tôi cũng có một lớp con của MyBase cần có thành viên MyVar loại vì cần gọi Foo. Di chuyển hàm Foo vào MyVarBase không phải là một tùy chọn. Có ý nghĩa không khi thực hiện việc này:

Điều này dường như hoạt động nhưng trông rất tệ và tôi không chắc liệu nó có gây rò rỉ bộ nhớ hay phá vỡ trình tạo bản sao hay không. Các tùy chọn khác của tôi có thể là đặt tên biến MyVar trong MyClass một cái gì đó khác nhưng có nó bằng với con trỏ m_var trong cơ sở, hoặc để templatise MyBase trên kiểu MyVar.

Tất cả các tùy chọn này không có vẻ lý tưởng vì vậy tôi muốn biết liệu có ai khác đã gặp phải tình huống như thế này không và liệu có cách nào tốt để làm cho nó hoạt động không.

Trả lời

13

Cách chính xác để thực hiện việc này là chỉ có biến trong lớp cơ sở. Khi lớp được thừa kế biết rằng đó phải năng động kiểu MyVar, điều này là hoàn toàn hợp lý:

class MyClass : public MyBase 
{ 
public: 
    MyClass(MyVar* v) : MyBase(v) {} 
    MyVar* GetVar() { return static_cast<MyVar*>(MyBase::GetVar()); } 
} 

Kể từ myVar có nguồn gốc từ MyVarBase, sự trở lại-loại khác nhau của GetVar sẽ vẫn làm việc nếu getVar là ảo (như là trường hợp ở đây). Lưu ý rằng với phương thức đó, chắc chắn không có hàm nào trong MyBase có thể đặt lại con trỏ thành một cái gì đó khác, rõ ràng.

Lưu ý rằng static_cast là phiên bản phù hợp trong trường hợp đó. Sử dụng dynamic_cast, như được đề xuất bởi một người nhận xét, sẽ cho người đọc và người dùng GetVar biết rằng MyBase::GetVar() có thể trả lại con trỏ tới một đối tượng không thuộc loại MyVar. Nhưng điều đó không phản ánh ý định của chúng tôi, vì bạn chỉ bao giờ vượt qua MyVar. Kết quả là điều quan trọng nhất trong phát triển phần mềm. Những gì bạn có thể làm là để khẳng định nó là không null. Nó sẽ hủy bỏ trong thời gian chạy với một lỗi tin nhắn trong debug-xây dựng của dự án của bạn:

MyVar* GetVar() { 
    assert(dynamic_cast<MyVar*>(MyBase::GetVar()) != 0); 
    return static_cast<MyVar*>(MyBase::GetVar()); 
} 
+0

Tôi sẽ sử dụng dynamic_cast <> chứ không phải static_cast <> trong trường hợp này. Tôi nghĩ rằng nó nắm bắt được ý định tốt hơn. – coryan

+0

Không chỉ vậy, nhưng sự trở lại của dynamic_cast <> sẽ trả về null nếu v không phải là MyVar. – KeithB

+1

không có coryan. dynamic_cast sẽ tệ hơn. vì độc giả nghĩ GetVar có thể trả lại MyVarBase, điều này sẽ sai –

2

Mà không biết thêm về bối cảnh, thật khó để nói chắc chắn, nhưng tôi muốn xem xét lại xem bạn cần phân cấp lớp này ngay từ đầu. Bạn có thực sự cần các lớp MyVarBase và MyBase không? Bạn có thể lấy đi với bố cục thay vì thừa kế không? Có lẽ bạn không cần chế độ thừa kế lớp, nếu bạn tạo mẫu cho các hàm và các lớp làm việc với các lớp ở trên. Có lẽ CRTP cũng có thể giúp bạn. Có rất nhiều lựa chọn thay thế để sử dụng các hàm ảo (và ở một mức độ nào đó, kế thừa nói chung) trong C++.

Dù sao, giải pháp được đề xuất của riêng bạn chắc chắn sẽ không bị rò rỉ bộ nhớ (vì nó không phân bổ bất kỳ bộ nhớ nào) và sẽ không phá vỡ hàm tạo bản sao mặc định (vì nó chỉ thực hiện bản sao thành viên. chỉ cần có thêm một thành viên trong lớp dẫn xuất, nhưng nó cũng được sao chép). Tất nhiên, bạn nhận được vấn đề bổ sung về việc giữ hai biến đồng bộ, vì vậy tôi vẫn không thích giải pháp đó nhiều.

Hoặc, bạn có thể xóa biến đó khỏi lớp cơ sở. Làm cho hàm GetVar() thuần ảo, vì vậy nó chỉ được thực hiện trong các lớp dẫn xuất, có thể định nghĩa biến thành viên theo bất kỳ cách nào chúng thích.

0

Tôi nghĩ nhắc lại mẫu có thể là một lựa chọn tốt, vì vậy cái gì đó như:

class MyVarBase 
{ 
}; 

class MyVar : public MyVarBase 
{ 
    int Foo(); 
}; 

template <class T> class MyBase 
{ 
public: 
    MyBase(T* v) : m_var(v) {} 
    T* GetVar() { return m_var; } 

private: 
    T* m_var; 
}; 

class MyClass : public MyBase<MyVar> 
{ 
public: 
    MyClass(MyVar* v) : MyBase(v) {} 
}; 

Tuy nhiên điều này sẽ phụ thuộc vào những gì lớp bạn thực sự có thể thay đổi. Ngoài ra, định nghĩa 'MyClass' có thể dư thừa, trừ khi nó có các thành viên khác trên và trên lớp MyBase.

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