Tôi đã chơi một chút với chủ đề này và đưa ra giải pháp thay thế trong trường hợp bạn sử dụng C++ 11. Hãy xem xét những điều sau:
class MyClass
{
public:
MyClass() :
expensiveObjectLazyAccess()
{
// Set initial behavior to initialize the expensive object when called.
expensiveObjectLazyAccess = [this]()
{
// Consider wrapping result in a shared_ptr if this is the owner of the expensive object.
auto result = std::shared_ptr<ExpensiveType>(CreateExpensiveObject());
// Maintain a local copy of the captured variable.
auto self = this;
// overwrite itself to a function which just returns the already initialized expensive object
// Note that all the captures of the lambda will be invalidated after this point, accessing them
// would result in undefined behavior. If the captured variables are needed after this they can be
// copied to local variable beforehand (i.e. self).
expensiveObjectLazyAccess = [result]() { return result.get(); };
// Initialization is done, call self again. I'm calling self->GetExpensiveObject() just to
// illustrate that it's safe to call method on local copy of this. Using this->GetExpensiveObject()
// would be undefined behavior since the reassignment above destroys the lambda captured
// variables. Alternatively I could just use:
// return result.get();
return self->GetExpensiveObject();
};
}
ExpensiveType* GetExpensiveObject() const
{
// Forward call to member function
return expensiveObjectLazyAccess();
}
private:
// hold a function returning the value instead of the value itself
std::function<ExpensiveType*()> expensiveObjectLazyAccess;
};
Ý tưởng chính là giữ chức năng trả về đối tượng đắt tiền làm thành viên thay vì đối tượng. Trong constructor khởi tạo với một chức năng nào sau đây:
- Khởi đối tượng đắt
- Thay thế bản thân với một chức năng mà chụp các đối tượng đã khởi tạo và chỉ trả về nó.
- Trả về đối tượng.
Điều tôi thích về điều này là mã khởi tạo vẫn được viết trong hàm tạo (nơi tôi tự nhiên đặt nó nếu không cần thiết) mặc dù nó sẽ chỉ được thực hiện khi truy vấn đầu tiên đối tượng xảy ra.
Một nhược điểm của phương pháp này là hàm std :: gán lại chính nó trong quá trình thực thi. Truy cập bất kỳ thành viên không tĩnh nào (bắt giữ trong trường hợp sử dụng lambda) sau khi phân công lại sẽ dẫn đến hành vi không xác định, vì vậy điều này đòi hỏi sự chú ý nhiều hơn. Đây cũng là loại hack vì GetExpensiveObject() là const nhưng nó vẫn thay đổi thuộc tính thành viên trong lần gọi đầu tiên.
Trong mã sản xuất, tôi có thể muốn chỉ làm cho thành viên có thể biến đổi thành James Curran được mô tả. Bằng cách này, API công khai của lớp bạn nêu rõ rằng thành viên không được coi là một phần của trạng thái đối tượng, do đó nó không ảnh hưởng đến constness.
Sau khi suy nghĩ thêm một chút, tôi thấy rằng std :: async với std :: launch :: deferred cũng có thể được sử dụng kết hợp với std :: shared_future để có thể truy xuất kết quả nhiều lần. Đây là mã số:
class MyClass
{
public:
MyClass() :
deferredObj()
{
deferredObj = std::async(std::launch::deferred, []()
{
return std::shared_ptr<ExpensiveType>(CreateExpensiveObject());
});
}
const ExpensiveType* GetExpensiveObject() const
{
return deferredObj.get().get();
}
private:
std::shared_future<std::shared_ptr<ExpensiveType>> deferredObj;
};
Tôi cũng thích câu trả lời của bạn. Việc triển khai giao diện con trỏ có thể nhất quán hơn giao diện tham chiếu. –
Điều này dường như là giải pháp phổ biến nhất "chơi đẹp" với thông số C++. Đó là xấu xí dưới bề mặt, nhưng API công cộng vẫn sạch sẽ. –
Tôi ngạc nhiên vì tôi không thể tìm thấy thư viện Boost cho việc này. –