为延迟初始化的成员变量实现getter方法并保持const正确性的正确方法是什么?也就是说,我喜欢让我的getter方法成为const,因为在第一次使用它之后,它是一个普通的getter方法。这是第一次(首次初始化对象时)const不适用。我想做什么:
class MyClass {
MyClass() : expensive_object_(NULL) {}
QObject* GetExpensiveObject() const {
if (!expensive_object_) {
expensive_object = CreateExpensiveObject();
}
return expensive_object_;
}
private:
QObject *expensive_object_;
};
我可以吃蛋糕吗?
答案 0 :(得分:22)
这很好,是典型的做法。
您必须将expensive_object_
声明为mutable
mutable QObject *expensive_object_;
mutable
基本上意味着“我知道我在const对象中,但修改它不会破坏常量。”
答案 1 :(得分:19)
如果您经常这样做,我建议将James Curran的答案封装到自己的类中:
template <typename T>
class suspension{
std::tr1::function<T()> initializer;
mutable T value;
mutable bool initialized;
public:
suspension(std::tr1::function<T()> init):
initializer(init),initialized(false){}
operator T const &() const{
return get();
}
T const & get() const{
if (!initialized){
value=initializer();
initialized=true;
}
return value;
}
};
现在在代码中使用它,如下所示:
class MyClass {
MyClass() : expensive_object_(CreateExpensiveObject) {}
QObject* GetExpensiveObject() const {
return expensive_object_.get();
}
private:
suspension<QObject *> expensive_object_;
};
答案 2 :(得分:6)
使expensive_object_
变为可变。
答案 3 :(得分:4)
使用const_cast
在该特定位置使用旁边的const。
QObject* GetExpensiveObject() const {
if (!expensive_object_) {
const_cast<QObject *>(expensive_object_) = CreateExpensiveObject();
}
return expensive_object_;
}
恕我直言,这比制作expensive_object_
mutable
更好,因为你不会在所有其他方法中失去const安全性。
答案 4 :(得分:3)
您是否考虑过包装类?你可能能够使用类似智能指针的东西,只有常量返回版本的operator*
和operator->
以及operator[]
......你可以获得scoped_ptr
这样的行为就像奖金一样。
让我们试一试,我相信人们可以指出一些缺陷:
template <typename T>
class deferred_create_ptr : boost::noncopyable {
private:
mutable T * m_pThing;
inline void createThingIfNeeded() const { if ( !m_pThing ) m_pThing = new T; }
public:
inline deferred_create_ptr() : m_pThing( NULL ) {}
inline ~deferred_create_ptr() { delete m_pThing; }
inline T * get() const { createThingIfNeeded(); return m_pThing; }
inline T & operator*() const { return *get(); }
inline T * operator->() const { return get(); }
// is this a good idea? unintended conversions?
inline T * operator T *() const { return get(); }
};
使用type_traits
可能会让这更好......
你需要不同版本的数组指针,如果你想将参数传递给T
的构造函数,你可能需要使用创建者仿函数或工厂对象或其他东西。
但你可以像这样使用它:
class MyClass {
public:
// don't need a constructor anymore, it comes up NULL automatically
QObject * getExpensiveObject() const { return expensive_object_; }
protected:
deferred_create_ptr<QObject> expensive_object_;
};
时间开始编译并查看我是否可以打破它... =)
答案 5 :(得分:0)
提出一个更加漂亮的解决方案here,但它不处理没有默认构造函数的类型......
答案 6 :(得分:0)
我已经创建了一个具有以下功能的课程模板Lazy<T>
:
以下是您如何使用它:
// Constructor takes function
Lazy<Expensive> lazy([] { return Expensive(42); });
// Multiple ways to access value
Expensive& a = *lazy;
Expensive& b = lazy.value();
auto c = lazy->member;
// Check if initialized
if (lazy) { /* ... */ }
这是实施。
#pragma once
#include <memory>
#include <mutex>
// Class template for lazy initialization.
// Copies use reference semantics.
template<typename T>
class Lazy {
// Shared state between copies
struct State {
std::function<T()> createValue;
std::once_flag initialized;
std::unique_ptr<T> value;
};
public:
using value_type = T;
Lazy() = default;
explicit Lazy(std::function<T()> createValue) {
state->createValue = createValue;
}
explicit operator bool() const {
return static_cast<bool>(state->value);
}
T& value() {
init();
return *state->value;
}
const T& value() const {
init();
return *state->value;
}
T* operator->() {
return &value();
}
const T* operator->() const {
return &value();
}
T& operator*() {
return value();
}
const T& operator*() const {
return value();
}
private:
void init() const {
std::call_once(state->initialized, [&] { state->value = std::make_unique<T>(state->createValue()); });
}
std::shared_ptr<State> state = std::make_shared<State>();
};
答案 7 :(得分:0)
我在这个主题上玩了一些,并提出了一个替代解决方案,以防你使用C ++ 11。请考虑以下事项:
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;
};
主要思想是持有一个函数,将昂贵的对象作为成员而不是对象本身返回。在构造函数中使用执行以下操作的函数进行初始化:
我喜欢这个是初始化代码仍然在构造函数中编写(如果不需要延迟,我自然会把它放在那里),即使它只会在昂贵对象的第一个查询发生时执行。
这种方法的缺点是std :: function在其执行中重新分配。在重新分配后访问任何非静态成员(在使用lambda的情况下捕获)将导致未定义的行为,因此这需要额外注意。这也是一种破解,因为GetExpensiveObject()是const但它仍然在第一次调用时修改成员属性。
在生产代码中,我可能更愿意将成员变为James Curran描述的可变成员。这样,您的类的公共API明确指出该成员不被视为对象状态的一部分,因此它不会影响常量。
经过一番思考后,我认为std :: async与std :: launch :: deferred也可以与std :: shared_future结合使用,以便能够多次检索结果。这是代码:
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;
};
答案 8 :(得分:-1)
你的getter不是const,因为它确实改变了对象的内容。我觉得你在想它。