簡體   English   中英

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

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

為延遲初始化的成員變量實現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_;
};

我可以吃蛋糕嗎?

這很好,是這種做法的典型方式。

您必須將expensive_object_聲明為mutable

mutable QObject *expensive_object_; 

mutable基本上意味着“我知道我在const對象中,但修改它不會破壞const。”

如果經常這樣做,我建議將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_;
};

使expensive_object_變得可變。

在一個特定的地方使用const_cast來支持const。

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

恕我直言,這是比更好地expensive_object_ mutable ,因為你不會失去你的所有其他方法的常量安全。

你考慮過包裝類嗎? 您可能能夠使用類似智能指針的東西,僅使用const返回版本的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_;
};

是時候開始編譯了,看看我是否可以打破它... =)

在這里提出一個更好的解決方案,但它不處理沒有默認構造函數的類型...

我創建了一個具有以下功能的類模板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>();
};

我玩了一下這個主題,並提出了一個替代解決方案,以防你使用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明確指出該成員不被視為對象狀態的一部分,因此它不會影響constness。

經過一番思考后,我認為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;
};

你的getter不是真正的const,因為它確實改變了對象的內容。 我覺得你在想它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM