C++ C++;具有延迟初始化的const-getter方法

C++ C++;具有延迟初始化的const-getter方法,c++,constants,lazy-loading,C++,Constants,Lazy Loading,为延迟初始化的成员变量实现getter方法并保持const正确性的正确方法是什么?也就是说,我希望我的getter方法是const,因为在第一次使用它之后,它是一个普通的getter方法。const仅在第一次(对象首次初始化时)不应用。我想做的是: class MyClass { MyClass() : expensive_object_(NULL) {} QObject* GetExpensiveObject() const { if (!expensive_object_)

为延迟初始化的成员变量实现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_;
};

我能把蛋糕吃了也吃了吗?

那很好,这是典型的做法

您必须将
昂贵的\u对象
声明为
可变的

mutable QObject *expensive_object_; 

mutable
基本上意味着“我知道我在一个常量对象中,但修改它不会破坏常量。”

使
昂贵的对象
可变。

使用
const\u cast
在一个特定的位置避开步长常量

QObject* GetExpensiveObject() const {
  if (!expensive_object_) {
    const_cast<QObject *>(expensive_object_) = CreateExpensiveObject();
  }
  return expensive_object_;
}
QObject*GetExpensiveObject()常量{
如果(!昂贵的对象){
const_cast(昂贵的对象)=CreateExpensiveObject();
}
返回昂贵的对象;
}
我想,这比使
昂贵的\u对象
可变
要好,因为在所有其他方法中都不会失去常量安全性。

如果您经常这样做,我建议将的答案封装到自己的类中:

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;
   }
};
模板
停课{
std::tr1::函数初始值设定项;
可变T值;
可变布尔初始化;
公众:
暂停(std::tr1::函数初始化):
初始值设定项(init),已初始化(false){
运算符T常数&()常数{
返回get();
}
T const&get()const{
如果(!已初始化){
值=初始值设定项();
初始化=真;
}
返回值;
}
};
现在,在您的代码中使用此选项,如下所示:

class MyClass {
  MyClass() : expensive_object_(CreateExpensiveObject) {}
  QObject* GetExpensiveObject() const {
    return expensive_object_.get();
  }
private:
  suspension<QObject *> expensive_object_;
};
class-MyClass{
MyClass():昂贵的对象(CreateExpensiveObject){}
QObject*GetExpensiveObject()常量{
返回昂贵的_对象_u.get();
}
私人:
悬浮物;
};

您考虑过包装类吗?您可能可以使用智能指针之类的工具,只使用const返回
operator*
operator->
以及
operator[]
的版本。。。您可以从中获得类似于
的行为作为奖励

让我们试一试,我相信人们可以指出一些缺陷:

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(); }
};

是时候开始编译了,看看我能不能破解它…=)

你的getter并不是真正的常量,因为它确实改变了对象的内容。我想你已经考虑过了。

提出了一个更为奇特的解决方案,但它不能处理没有默认构造函数的类型…

我已经创建了一个类模板
Lazy
,具有以下特性:

  • 类似于标准智能指针的熟悉界面
  • 支持没有默认构造函数的类型
  • 支持不带复制构造函数的(可移动)类型
  • 线程安全
  • 使用引用语义可复制:所有副本共享相同的状态;它们的价值只能创造一次
以下是您如何使用它:

// 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) { /* ... */ }
//构造函数接受函数
懒惰懒惰([]{返回昂贵(42);});
//获取价值的多种方法
昂贵&a=*懒惰;
昂贵&b=lazy.value();
自动c=懒惰->成员;
//检查是否已初始化
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>();
};
#pragma一次
#包括
#包括
//用于延迟初始化的类模板。
//副本使用引用语义。
模板
班级懒惰{
//副本之间的共享状态
结构状态{
std::函数createValue;
std::一旦_标志初始化;
std::唯一的ptr值;
};
公众:
使用值_type=T;
Lazy()=默认值;
显式延迟(std::函数createValue){
状态->createValue=createValue;
}
显式运算符bool()常量{
返回静态转换(状态->值);
}
T&value(){
init();
返回*状态->值;
}
常量T&值()常量{
init();
返回*状态->值;
}
T*运算符->(){
返回&值();
}
常量T*运算符->()常量{
返回&值();
}
T&运算符*(){
返回值();
}
常量T&运算符*()常量{
返回值();
}
私人:
void init()常量{
std::调用_一次(state->initialized,[&]{state->value=std::使_唯一(state->createValue());});
}
std::shared_ptr state=std::make_shared();
};

我对这个主题做了一些探讨,并提出了一个替代解决方案,以防您使用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;
};
class-MyClass
{
公众:
MyClass():
expensiveObjectLazyAccess()
{
//设置初始行为以在调用时初始化昂贵的对象。
expensiveObjectLazyAccess=[此]()
{
/如果这是昂贵对象的所有者,则考虑在SyrdypTR中包装结果。
自动结果=std::shared_ptr(CreateExpensiveObject());
//维护捕获变量的本地副本。
自动自我=此;
//将自身重写为只返回已初始化对象的函数
//请注意,在访问lambda之后,lambda的所有捕获都将失效
//将导致未定义的行为。如果在此之后需要捕获的变量,则可以
//预先复制到局部变量(即self)。
expensiveObjectLazyAccess=[result](){return result.get();};
//初始化完成后,再次调用self。我调用self->GetExpensiveObject()只是为了
//说明在本地副本上调用方法是安全的
//将是未定义的行为,因为上述重新分配会破坏捕获的lambda
//变量。或者,我可以使用:
//返回result.get();
返回自我->通用电气
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;
};
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;
};