简体   繁体   English

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

[英]C++ const getter method with lazy initialization

What is the proper way to implement a getter method for a lazily-initialized member variable and maintain const-correctness? 为延迟初始化的成员变量实现getter方法并保持const正确性的正确方法是什么? That is, I would like to have my getter method be const, because after the first time it is used, it's a normal getter method. 也就是说,我想让我的getter方法成为const,因为在第一次使用它之后,它是一个普通的getter方法。 It is only the first time (when the object is first initialized) that const does not apply. 这是第一次(首次初始化对象时)const不适用。 What I would like to do: 我想做什么:

class MyClass {
  MyClass() : expensive_object_(NULL) {}
  QObject* GetExpensiveObject() const {
    if (!expensive_object_) {
      expensive_object = CreateExpensiveObject();
    }
    return expensive_object_;
  }
private:
  QObject *expensive_object_;
};

Can I eat my cake and have it too? 我可以吃蛋糕吗?

That's fine and is the typical way of doing it. 这很好,是这种做法的典型方式。

You will have to declare expensive_object_ as mutable 您必须将expensive_object_声明为mutable

mutable QObject *expensive_object_; 

mutable basically means "I know I'm in a const object, but modifying this won't break const-ness." mutable基本上意味着“我知道我在const对象中,但修改它不会破坏const。”

I propose encapsulating James Curran 's answer into a class of its own if you do this frequently: 如果经常这样做,我建议将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;
   }
};

Now use this in your code as follows: 现在在代码中使用它,如下所示:

class MyClass {
  MyClass() : expensive_object_(CreateExpensiveObject) {}
  QObject* GetExpensiveObject() const {
    return expensive_object_.get();
  }
private:
  suspension<QObject *> expensive_object_;
};

使expensive_object_变得可变。

Use a const_cast to side-step const in that one specific place. 在一个特定的地方使用const_cast来支持const。

QObject* GetExpensiveObject() const {
  if (!expensive_object_) {
    const_cast<QObject *>(expensive_object_) = CreateExpensiveObject();
  }
  return expensive_object_;
}

IMHO, this is better than making expensive_object_ mutable because you don't lose the const-safety in all your other methods. 恕我直言,这是比更好地expensive_object_ mutable ,因为你不会失去你的所有其他方法的常量安全。

Have you considered a wrapper class? 你考虑过包装类吗? You might be able to get away with something like a smart pointer, with only const-returning versions of operator* and operator-> and maybe operator[] ... You can get scoped_ptr -like behavior out of it as a bonus. 您可能能够使用类似智能指针的东西,仅使用const返回版本的operator*operator->以及可能的operator[] ......您可以获得类似scoped_ptr的行为作为奖励。

Let's give this a shot, I'm sure people can point out a few flaws: 让我们试一试,我相信人们可以指出一些缺陷:

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

Use of type_traits might make this better... 使用type_traits可能会让这更好......

You'd need different versions for array pointers, and you might have to play around a bit with a creator functor or factory object or something if you wanted to pass in arguments to T 's constructor. 你需要不同版本的数组指针,如果你想将参数传递给T的构造函数,你可能需要使用创建者仿函数或工厂对象或其他东西。

But you could use it like this: 但你可以像这样使用它:

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_;
};

Time to go off and compile this and see if I can break it... =) 是时候开始编译了,看看我是否可以打破它... =)

在这里提出一个更好的解决方案,但它不处理没有默认构造函数的类型...

I've crated a class template Lazy<T> with the following features: 我创建了一个具有以下功能的类模板Lazy<T>

  • Familiar interface similar to standard smart pointers 熟悉的界面类似于标准的智能指针
  • Supports types without default constructor 支持没有默认构造函数的类型
  • Supports (movable) types without copy constructor 支持(可移动)类型,没有复制构造函数
  • Thread-safe 线程安全
  • Copyable using reference semantics: All copies share the same state; 使用引用语义进行复制:所有副本共享相同的状态; their value is created only once. 它们的值只创建一次。

Here's how you use it: 以下是您使用它的方式:

// 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) { /* ... */ }

Here's the implementation. 这是实施。

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

I played a bit with this topic and came up with an alternative solution in case you use C++11. 我玩了一下这个主题,并提出了一个替代解决方案,以防你使用C ++ 11。 Consider the following: 考虑以下:

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

Main idea is to hold a function returning the expensive object as member instead of the object itself. 主要思想是持有一个函数,将昂贵的对象作为成员而不是对象本身返回。 In the constructor initialize with a function that does the following: 在构造函数中使用执行以下操作的函数进行初始化:

  • Initializes the expensive object 初始化昂贵的对象
  • Replaces itself with a function which captures the already initialized object and just returns it. 用一个捕获已经初始化的对象并返回它的函数替换它自己。
  • Returns the object. 返回对象。

What I like about this is that the initialization code is still written in the constructor (where I'd naturally put it if lazyness was not needed) even though it will only get executed when the first query for the expensive object happens. 我喜欢这个是初始化代码仍然写在构造函数中(如果不需要延迟,我自然会把它放在那里),即使它只会在昂贵对象的第一个查询发生时执行。

A downside of this approach is that std::function reassigns itself within it's execution. 这种方法的缺点是std :: function在其执行中重新分配。 Accessing any non static members (captures in case of using lambda's) after the reassignment would result in undefined behavior so this requires extra attention. 在重新分配后访问任何非静态成员(在使用lambda的情况下捕获)将导致未定义的行为,因此这需要额外注意。 Also this is kind of a hack since GetExpensiveObject() is const but it still modifies a member attribute on first call. 这也是一种破解,因为GetExpensiveObject()是const但它仍然在第一次调用时修改成员属性。

In production code I'd probably prefer to just make the member mutable as James Curran described. 在生产代码中,我可能更愿意像James Curran所描述的那样让成员变得可变。 This way the public API of your class clearly states that the member is not considered part of the objects state, therefore it doesn't affect constness. 这样,您的类的公共API明确指出该成员不被视为对象状态的一部分,因此它不会影响constness。

After a bit more thinking I figured that std::async with std::launch::deferred could also be used in combination with a std::shared_future in order to be able to retrieve the result multiple times. 经过一番思考后,我认为std :: async与std :: launch :: deferred也可以与std :: shared_future结合使用,以便能够多次检索结果。 Here is the code: 这是代码:

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

Your getter isn't really const since it does change the content of the object. 你的getter不是真正的const,因为它确实改变了对象的内容。 I think you're over thinking it. 我觉得你在想它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM